<?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: Vincent Ge</title>
    <description>The latest articles on DEV Community by Vincent Ge (@gewenyu99).</description>
    <link>https://dev.to/gewenyu99</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%2F785242%2F7c4f39ff-7e9b-41ba-a9e2-d426c308a64d.jpeg</url>
      <title>DEV Community: Vincent Ge</title>
      <link>https://dev.to/gewenyu99</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gewenyu99"/>
    <language>en</language>
    <item>
      <title>How to detect and fix flaky tests in Pytest</title>
      <dc:creator>Vincent Ge</dc:creator>
      <pubDate>Tue, 11 Mar 2025 17:57:32 +0000</pubDate>
      <link>https://dev.to/gewenyu99/how-to-detect-and-fix-flaky-tests-in-pytest-1k61</link>
      <guid>https://dev.to/gewenyu99/how-to-detect-and-fix-flaky-tests-in-pytest-1k61</guid>
      <description>&lt;h2&gt;
  
  
  What are flaky tests?
&lt;/h2&gt;

&lt;p&gt;Flaky tests produce different results each time you run them. This means you can rerun the same CI job on the same commit 10 times and see inconsistent results. These flaky tests are the tests that fail and block your PRs when you fix a typo or bump a dependency, even though your changes couldn’t have possibly broken a test. They’re the tests that make you habitually hit the rerun job button when tests fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why do flaky tests occur?
&lt;/h2&gt;

&lt;p&gt;Flaky tests are most common in integration tests and e2e (end-to-end) tests. These tests tend to be complex and can be flaky for a multitude of reasons. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Flaky test code (bad)&lt;/li&gt;
&lt;li&gt;Flaky application code (really bad)&lt;/li&gt;
&lt;li&gt;Flaky infrastructure (pain 🙃)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This flakiness is inevitable to some degree. You write complex e2e and integration tests to test your complex system. These more complex tests give you a higher degree of confidence about your complex system. This is why the best teams in the world write e2e tests. It’s a conscious tradeoff of complexity, flakiness, and confidence where you write more complex and potentially more flaky tests for better confidence that tests validate the complete system.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test Type&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;th&gt;Confidence&lt;/th&gt;
&lt;th&gt;Flakiness&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unit tests&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integration tests&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;End-to-End tests&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We can break down the causes of flaky tests into a few common categories. Let me give you some examples.&lt;/p&gt;

&lt;h3&gt;
  
  
  Machine learning and data science
&lt;/h3&gt;

&lt;p&gt;Python has a special and common use case for training and deploying machine learning models. The problem with applications of this nature is that they're probabilistic in nature. If you're testing training code for a model or regression on a data set, the convergence of a model given fixed parameters is non-deterministic unless you're willing to let the model train for an impractical number of epochs.&lt;/p&gt;

&lt;p&gt;What this means is that flakiness in machine learning and some data science applications is &lt;a href="https://www.cs.cornell.edu/~saikatd/papers/flash-issta20.pdf" rel="noopener noreferrer"&gt;almost inevitable&lt;/a&gt;. The best you can do in these cases is to tweak the assertion criteria so the tests are less flaky. We'll also discuss how you can do this in a &lt;a href="https://trunk.io/blog/how-to-avoid-and-detect-flaky-tests-in-pytest#fixing-flaky-tests" rel="noopener noreferrer"&gt;later section&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Concurrency and Test Order Dependencies
&lt;/h3&gt;

&lt;p&gt;When tests are run concurrently and in random order, tests that are otherwise not flaky can become flaky due to isolation problems.Consider the following scenario where test A updates a row in the table and test B deletes the row. When executed alone, these tests may produce deterministic results. When executed concurrently, they can become flaky if test B deletes the row updated by test A before test A’s assertion completes.&lt;/p&gt;

&lt;p&gt;Consider another scenario when test A and test B are executed in different order, they could affect each other’s results. If test B deletes the row updated by test A before test A runs, the test could fail. The &lt;strong&gt;timing&lt;/strong&gt; in which these tests are executed affects their results.&lt;/p&gt;

&lt;h3&gt;
  
  
  External resources
&lt;/h3&gt;

&lt;p&gt;If you’re to be truly confident in the results of your tests, you need tests that cover parts of your entire system. This sometimes means external resources that &lt;strong&gt;aren’t set up and taken down with your test run&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;This commonly means running tests against a dev or staging environment, against external databases and APIs, or interacting with a third-party service. These systems’ states are inconsistent from run to run. Whether it’s a QA engineer who unknowingly updates a row that your e2e tests depend on or it’s an external API timing out because of slow network performance, these resources are unreliable. Unreliable external resources create flaky tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lack of isolation
&lt;/h3&gt;

&lt;p&gt;Your tests should ideally be isolated. This means they’re independent of each other and they’re independent from run to run. (Good) Tests read, create, update, and delete artifacts in the form of database entries, files uploaded, users created, and background job scheduled. This is great for write tests that are realistic and inspire confidence, but these artifacts need to be properly cleaned up so they don’t impact the results of future runs.&lt;/p&gt;

&lt;p&gt;To have truly independent tests, you need to be running them on a fresh setup each time. But you can’t reasonably create an entire VM or clusters of VMs to run each test, which is how you’d achieve perfect isolation test to test. You sometimes can’t even run each CI job against a fresh setup. If you tried, you’d either be paying buckets for CI resources or you’re waiting for hours to await resources on a busy day.&lt;/p&gt;

&lt;p&gt;The alternative is to have setup and tear down between tests, test suites, or CI jobs. The problem here, is that they’re difficult to get perfect. You might forget some teardown logic here and there, some bits of orphaned code might sprinkle random artifacts, and jobs that don’t run to completion due to crashes or cancellations will also leave behind a mess. The build-up of these unexpected artifacts will cause flakiness.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inconsistent runtimes
&lt;/h3&gt;

&lt;p&gt;Each CI machine will perform differently. This could be due to differences in CPU performance, disk performance, RAM available, and network load. This could cause some seriously annoying to debug problems, usually involving out-of-memory issues or tests timing out because a particular CI machine is slower.&lt;/p&gt;

&lt;p&gt;These are extra annoying to fix because they’re impossible to reproduce locally. After all, your development machine is probably several times more powerful than any CI machine. These problems are also more common if your tests involve physical devices, such as phones and watches for native development or GPUs for AI and simulations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Buggy production code
&lt;/h3&gt;

&lt;p&gt;Sometimes, the root of flakiness isn’t in your test code but in your production code. This means you have actual bugs to deal with. You might have queries or APIs that occasionally fail in production. You might have discovered unhandled edge cases. You may have stumbled upon problems with your infrastructure if you test on a development or staging environment.&lt;/p&gt;

&lt;p&gt;This is probably the most concerning type of flaky test, but also the hardest to fix. You want to fix these problems since this inconsistent behavior could affect paying customers. Fixing these problems is often difficult because they might result from multiple moving parts in a larger system. We'll discuss how to best approach these difficult fixes &lt;a href="https://trunk.io/blog/how-to-avoid-and-detect-flaky-tests-in-pytest#fixing-flaky-tests" rel="noopener noreferrer"&gt;in a later section&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why should I be concerned?
&lt;/h2&gt;

&lt;p&gt;There’s good reason to be concerned about flaky tests. Flaky tests are rarely the result of bad test code alone. They could indicate flakiness in your infrastructure and production code. For many teams, this is the single biggest reason for investigating and fixing every flaky test.&lt;/p&gt;

&lt;p&gt;You might also notice the impact of flaky tests on the reliability of your CI. You rely on CI as guardrails so you can ship code quickly without worrying about introducing regressions or breaking existing features. Flaky tests can both ruin your trust in CI results and seriously slow down your team. If your CI jobs fail 20% of the time, you have to choose between rerunning your tests or ignoring flaky failures which forces you to choose between slowing your team down or poisoning your test culture.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Flaky tests are &lt;strong&gt;poison&lt;/strong&gt;. You can have the best tests in the world, the most sophisticated testing that finds the craziest bugs no one would think of, but if developers can’t trust the results, they can’t trust the results 99% of the time, &lt;strong&gt;it doesn’t matter&lt;/strong&gt;."&lt;br&gt;
&lt;em&gt;Roy Williams, Meta&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Dealing with Flaky Tests
&lt;/h2&gt;

&lt;p&gt;Flaky tests are inevitable if you write e2e tests at scale. Your software is complex, so you need some complex tests to provide confidence and validate your changes. If your tests are complex, you will have some flakiness in your tests. It’s a matter of economics; you can make your tests complex and reliable, but that will require many engineering hours that you may be unable to justify. Instead, you should focus on reducing the impact of flaky tests and tackle high-impact flaky tests efficiently.&lt;/p&gt;

&lt;p&gt;To effectively reduce the impact of flaky tests, you should do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoid flaky tests by learning common anti-patterns&lt;/li&gt;
&lt;li&gt;Detect flaky tests with automated tools and communicate them with your team&lt;/li&gt;
&lt;li&gt;Quarantine known flaky tests to mitigate it’s impact on the team&lt;/li&gt;
&lt;li&gt;Fix high impact flaky tests for the bang for your buck&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s walk through each of these steps in more detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoiding Flaky Tests in Pytest
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Avoid overly strict assertions
&lt;/h3&gt;

&lt;p&gt;One of the weird quirks of Python floating point arithmetic is that numbers that logically should be equal, are sometimes not. Take the following example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;
&lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;
&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is due to the way &lt;a href="https://www.geeksforgeeks.org/floating-point-error-in-python/" rel="noopener noreferrer"&gt;floating point numbers are represented&lt;/a&gt; in memory, certain numbers like 0.3 are stored as a number very close to the original value (0.30000000000000004), but not the exact same. This problem also exists in other languages like &lt;a href="https://0.30000000000000004.com/" rel="noopener noreferrer"&gt;JavaScript and C++&lt;/a&gt;, but is much more likely to cause problems in common applications of Python like &lt;a href="https://www.cs.cornell.edu/~saikatd/papers/flash-issta20.pdf" rel="noopener noreferrer"&gt;machine learning and data science&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Pytest provides a great way to combat floating point errors with the &lt;code&gt;approx()&lt;/code&gt; method. You can use it on floating point assertions and even &lt;code&gt;numpy&lt;/code&gt; arrays:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;approx&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;approx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;approx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; 
&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also assign relative and absolute tolerances for these approximations, which you can read more about in the &lt;br&gt;
Pytest docs on approx().&lt;/p&gt;

&lt;p&gt;Another reason to avoid overly strict assertions is for machine learning applications. The convergence of some algorithms, even over a consistent data set, is non-deterministic. You will have to tweak these assertions to be a good approximation of "good enough" to avoid excessive flakiness.&lt;/p&gt;
&lt;h3&gt;
  
  
  Avoid hard awaits
&lt;/h3&gt;

&lt;p&gt;Asynchronous programming in Python is complicated due to the limitations of the Python interpreter, but it hasn’t stopped Python developers from trying. The problem with the many libraries that do introduce multi-threading, multi-processing, and event-loop based &lt;code&gt;asyncio&lt;/code&gt; is that they’re more awkward to test in Python. We’re gonna take &lt;code&gt;asyncio&lt;/code&gt; as an example here, but the same principles apply to other implementations of async or multi-threaded programming in Python.&lt;/p&gt;

&lt;p&gt;In Pytest, if you try to test an async method using &lt;code&gt;asyncio&lt;/code&gt; naively, the test will fail because Pytest doesn’t support async operations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;async_method&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;async_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;## This will return false because 
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The best solution is to use a library like &lt;a href="https://pytest-asyncio.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;pytest-asyncio&lt;/a&gt; to properly support async operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@pytest.mark.asyncio&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
   &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Properly awaiting for async operations will usually result in a lower chance of flakiness in Python.&lt;/p&gt;

&lt;p&gt;What often causes flakiness when dealing with async operations is naively waiting for some time. This approach is intuitive but will be flaky because async operations don’t return in a deterministic amount of time. You can try stretching your sleep time longer and longer, which might become more reliable, but your test suites will take ages to run.&lt;/p&gt;

&lt;p&gt;Naively waiting will almost always cause some degree of flakiness, so if you see code snippets like this in your test suite, fix them immediately:&lt;/p&gt;

&lt;p&gt;from example import async_method&lt;br&gt;
import time&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;async_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;## This will be flaky
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should also avoid mocking the async code for testing purposes. Mocking async introduces unnecessary complexity and reduces your confidence level in your test by making it less realistic. &lt;/p&gt;

&lt;h3&gt;
  
  
  Control your test environment
&lt;/h3&gt;

&lt;p&gt;If your test environment changes from run to run, your tests are much more likely to be flaky. Your Pytest e2e and integration tests will likely depend on your entire system's state to produce consistent results. If you test on a persistent test environment or a shared dev/staging environment, that environment might differ each time the test is run.&lt;/p&gt;

&lt;p&gt;This is particularly problematic if you're using a library like &lt;a href="https://github.com/pytest-dev/pytest-xdist" rel="noopener noreferrer"&gt;pytest-xdist&lt;/a&gt; to distribute your tests across multiple CPUs. Testing in parallel and in random order introduces opportunities for both inconsistent system states to cause flakiness and race conditions where concurrently running tests interfere with each other.&lt;/p&gt;

&lt;p&gt;If you're using a persistent testing environment, you must ensure that artifacts created by your tests, such as new database tables and rows, files created in storage, and new background jobs started, are properly cleaned up. Leftover data from run to run might affect the results of future test runs.&lt;/p&gt;

&lt;p&gt;Similarly, you should be careful when testing a development or staging environment. These environments might have other developers who create and destroy data as part of their development process. This constantly changing environment can cause flakiness if other developers accidentally update or delete tables and files used during testing, create resources with unique IDs that collide with a test, or use up the environment's compute resources causing the test to timeout.&lt;/p&gt;

&lt;p&gt;The easiest way to avoid these potential problems is to use a fresh environment whenever possible when you run your tests. A transient environment set up and destroyed at the end of each test run will help ensure that each run's environment is consistent and no artifacts are left behind. If that's impossible, pay close attention to how you design your tests' setup and takedown to remove all dependencies. Each test should set up and take down its own resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limit External Resources
&lt;/h3&gt;

&lt;p&gt;While it's important to test all parts of your app fully, it's important to limit the number of tests that involve external resources, like external APIs and third-party integrations. These external resources may go down, they might change, and you may hit rate limits. They're all potential sources of flaky failures. You should aim to cover each external resource in a few tests, but avoid involving them in your tests excessively.&lt;/p&gt;

&lt;p&gt;Mocking is a decent strategy for testing specific behaviors, but it should not be used as a replacement for end-to-end testing. You can't entirely avoid external resources, but you should be mindful of which test suites involve these resources. Have some dedicated tests that involve external resources and mock them for other tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic Reruns
&lt;/h3&gt;

&lt;p&gt;If you only have a few flaky tests, rerunning tests on failure to clear flakiness can be effective. Libraries like &lt;a href="https://github.com/pytest-dev/pytest-rerunfailures" rel="noopener noreferrer"&gt;pytest-rerunfailures&lt;/a&gt; let you configure reruns easily, which reruns each test on the first failure to see if it's just flaky.&lt;/p&gt;

&lt;p&gt;This approach does have its own tradeoffs. It will cost more CI resources and CI time, which depending on the size of your test suite can be a negligible or profound problem. If you have &lt;a href="https://testing.googleblog.com/2017/04/where-do-our-flaky-tests-come-from.html#:~:text=Flaky%20Tests%20at%20Google,significant%20drag%20on%20our%20engineers." rel="noopener noreferrer"&gt;hundreds of thousands of tests&lt;/a&gt;, and a few thousand of them are flaky, this will be unbelievably expensive. You will also still catch flaky tests slipping through the cracks occasionally by pure chance.&lt;/p&gt;

&lt;p&gt;A much bigger problem is reruns just cover up the problem. When you introduce new flaky tests, you may not even notice. This is bad because some flaky tests indicate real problems with your code and the silent build-up of flaky tests can cause scaling problems within your CI system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detecting Flaky Tests in Pytest
&lt;/h2&gt;

&lt;p&gt;If you only have a few flaky tests, a great place to start is by having a central place to report them, such as a simple spreadsheet. To identify flaky tests, you can use commit SHAs and test retries. A commit SHA is a unique identifier for a specific commit in your version control system, like Git. If a test passes for one commit SHA but fails for the same SHA later, it's likely flaky since nothing has changed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1736807786-screenshot-2025-01-07-at-10-53-34-am.png%26w%3D2048%26q%3D90" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1736807786-screenshot-2025-01-07-at-10-53-34-am.png%26w%3D2048%26q%3D90" alt="Screenshot of flaky test tracking" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dropbox open-sourced a solution that helps you find flaky tests called &lt;a href="https://github.com/dropbox/pytest-flakefinder" rel="noopener noreferrer"&gt;pytest-flakefinder&lt;/a&gt;. This allows you to automatically rerun each test multiple times to find flaky tests. By default, the tool will run each test 50 times on the same commit and validate if the results are consistent. On the same commit, the code should be identical from run to run. If a test is not producing consistent results, it is considered flaky.&lt;/p&gt;

&lt;p&gt;Running tests multiple times to find flaky tests is a great way to audit your existing repo or new suites of tests. The problem with using a tool like this is that it relies on a "periodic cleanup" type workflow, instead of monitoring continuously. Relying on finding time to periodically clean up tech debt rarely works. Other &lt;em&gt;higher-priority&lt;/em&gt; tasks, typically anything but testing, will displace this workflow and you'll end up with a pileup of flaky tests again.&lt;/p&gt;

&lt;p&gt;Continuously monitoring flaky tests also gives the benefit of catching flaky tests due to changes in external resources, tool rot, and other sources of flakiness that happen due to drift and not code change. These problems are often easier to fix when caught early. Continuous monitoring also tells you if your fixes actually worked or not, since most flaky tests aren't fixed completely on the first attempt but will see reduced flakiness.&lt;/p&gt;

&lt;p&gt;Some form of continuous monitoring is especially important for Python applications that test ML or data science-related code. As we mentioned before, many ML and data science applications are inherently non-deterministic, and the best fix for these flaky tests is to just &lt;a href="https://www.cs.cornell.edu/~saikatd/papers/flash-issta20.pdf" rel="noopener noreferrer"&gt;tweak the assertion parameters&lt;/a&gt; so they're &lt;em&gt;acceptably flaky&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Collecting test results in CI and comparing results on the same commit is a great starting point for detecting flaky tests continuously and automatically. There are some nuances to consider for the different branches on which the tests are run. You should assume that the test should not normally fail on the &lt;code&gt;main&lt;/code&gt; branch (or any other stable branch) but can be expected to fail more frequently on your PR and feature branches. How much you factor different branches' flakiness signals will depend on your team's circumstances.&lt;/p&gt;

&lt;p&gt;If you're looking for a tool for this, &lt;a href="https://trunk.io/flaky-tests" rel="noopener noreferrer"&gt;Trunk Flaky Tests&lt;/a&gt; automatically detects and tracks flaky tests for you. Trunk also aggregates and displays the detected flaky tests, their past results, relevant stack traces, and flaky failure summaries on a single dashboard.&lt;/p&gt;

&lt;p&gt;For example, you'll get a &lt;a href="https://github.com/metabase/metabase/pull/51804#issuecomment-2574180118" rel="noopener noreferrer"&gt;GitHub comment&lt;/a&gt; on each PR, calling out if a flaky test caused the CI jobs to fail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1736808417-metabase-flaky-comment-light-2.png%26w%3D2048%26q%3D90" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1736808417-metabase-flaky-comment-light-2.png%26w%3D2048%26q%3D90" alt="Metabase flaky test comment" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Quarantining Flaky Tests in Pytest
&lt;/h2&gt;

&lt;p&gt;What do you do with flaky tests after you detect them? In an ideal world, you'd fix them immediately. In reality, they're usually sent to the back of your backlog. You have project deadlines to meet and products to deliver, all of which deliver more business value than fixing a flaky test. What's most likely is that you'll always have some known but unfixed flaky tests in your repo, so the goal is to reduce their impact before you can fix them.&lt;/p&gt;

&lt;p&gt;We've written in a &lt;a href="https://trunk.io/blog/what-we-learned-from-analyzing-20-2-million-ci-jobs-in-trunk-flaky-tests-part-1" rel="noopener noreferrer"&gt;past blog&lt;/a&gt; that flaky tests are harmful for most teams because they block PRs and reduce trust in tests. So once you know a test to be flaky, it's important to stop it from producing noise in CI and blocking PRs. We recommend you do this by quarantining these tests.&lt;/p&gt;

&lt;p&gt;Quarantining is the process of continuing to run a flaky test in CI without allowing failures to block PR merge. We recommend this method over disabling or deleting tests because disabled tests are usually forgotten and &lt;a href="https://dev.to/mlapierre/pros-and-cons-of-quarantined-tests-2emj"&gt;swept under the rug&lt;/a&gt;. We want our tests to produce less noise, not 0 noise. &lt;/p&gt;

&lt;p&gt;It's important to note that &lt;a href="https://dl.acm.org/doi/abs/10.1145/3377811.3381749" rel="noopener noreferrer"&gt;studies&lt;/a&gt; have shown that initial attempted fixes for flaky tests usually don't succeed. You need to have a historical record to know if the fix reduced the flake rate or completely fixed the flaky test. This is another reason why we believe you should keep running tests quarantined.&lt;/p&gt;

&lt;p&gt;To quarantine tests in Pytest, you can mark them as quarantined and run them in a separate CI job that doesn't block your PRs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@pytest.mark.quarantined&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;flaky_test&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Generates a random float between 0 and 1
&lt;/span&gt;    &lt;span class="n"&gt;random_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;random_value&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then run them with &lt;code&gt;pytest -v -m "quarantined"&lt;/code&gt; and exclude them with &lt;code&gt;pytest -v -m "not quarantined"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Some limitations here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This requires a code change to quarantine instead of happening dynamically at runtime.&lt;/li&gt;
&lt;li&gt;Doesn't integrate with automated systems to detect flaky tests and quarantine them.&lt;/li&gt;
&lt;li&gt;They produce very little noise and are easily forgotten.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A better way to approach this is to quarantine at runtime, which means to quarantine failures without updating the code. As tests are labeled flaky or return to a healthy status, they should be quarantined and unquarantined automatically. This is especially important for large repos with many contributors. A possible approach to accomplish this is to host a record of known flaky tests, run all tests, and then check if all failures are due to known flaky tests. If all tests are from known flaky tests, override the exit code of the CI job to pass.&lt;/p&gt;

&lt;p&gt;Again, this is really useful for ML and data science applications, where models become more or less flaky when updated. You might just want to quarantine certain tests that are flaky because the models don't converge in a short enough amount of time. Since they still run, you can track their failure rate to make sure they're not degrading and inspect the failure logs to catch actual errors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1740348022-test-run-detail-light.png%26w%3D2048%26q%3D90" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1740348022-test-run-detail-light.png%26w%3D2048%26q%3D90" alt="Test run detail screenshot" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're looking for a way to quarantine flaky tests at runtime, &lt;a href="https://trunk.io/flaky-tests" rel="noopener noreferrer"&gt;Trunk&lt;/a&gt; can help you here. Trunk will check if failed tests are known to be flaky and unblock your PRs if all failures can be quarantined.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.trunk.io/flaky-tests/quarantining" rel="noopener noreferrer"&gt;Learn more about Trunk's Quarantining&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing Flaky Tests
&lt;/h2&gt;

&lt;p&gt;We've covered some common anti-patterns earlier to help you avoid flaky tests, but if your flaky test is due to a more complex cause, how you approach a fix will vary heavily. We can't show you how to fix every way your tests flake; it can be very complex. Instead, let's cover &lt;strong&gt;prioritizing&lt;/strong&gt; which tests to fix and reproducing flaky tests.&lt;/p&gt;

&lt;p&gt;When deciding on which tests to fix first, we first need a way to rank them by their impact. What we discovered to be a good measure of impact when we &lt;a href="https://trunk.io/blog/what-we-learned-from-analyzing-20-2-million-ci-jobs-in-trunk-flaky-tests-part-1" rel="noopener noreferrer"&gt;worked with our partners&lt;/a&gt; is that flaky tests &lt;strong&gt;blocking the most PRs&lt;/strong&gt; should be fixed first. Ultimately, we want to eliminate flaky tests because they block PRs from being merged when they fail in CI. You can do this by tracking the number of times a known flaky test fails in CI on PR branches, either manually for smaller projects or automatically with a tool.&lt;/p&gt;

&lt;p&gt;This also helps you justify the engineering effort put towards fixing flaky tests, because with tech-debt, justifying for time invested is often a bigger blocker than the fix itself. When you reduce the number of blocked PRs, you save expensive engineering hours. You can further extrapolate the number of engineering hours saved by factoring in context-switching costs, which some studies show to be &lt;a href="https://ics.uci.edu/~gmark/chi08-mark.pdf" rel="noopener noreferrer"&gt;~23 minutes per context switch&lt;/a&gt; for knowledge workers. &lt;/p&gt;

&lt;p&gt;This approach is especially true for flakiness in ML and data science apps, where &lt;a href="https://www.cs.cornell.edu/~saikatd/papers/flash-issta20.pdf" rel="noopener noreferrer"&gt;a complete fix is impractical&lt;/a&gt;. For these applications, a fix usually just finds the high-impact flaky tests and reduces how strict the assertions are.&lt;/p&gt;

&lt;p&gt;After finding the high-impact tests to fix first, you can use a library like &lt;a href="https://github.com/ESSS/pytest-replay" rel="noopener noreferrer"&gt;pytest-replay&lt;/a&gt; to help reproduce tests locally. The difficult-to-reproduce tests often reveal real problems with your infrastructure or code. Pytest-replay lets you replay failures from replay records saved in CI, which can be a real-time save.&lt;/p&gt;

&lt;p&gt;If you're looking for a straightforward way to report flaky tests for &lt;em&gt;any language or framework&lt;/em&gt;, see their impact on your team, find the highest impact tests to fix, and track past failures and stack traces, you can try Trunk Flaky Tests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1733164003-individual-test-dashboard-light.png%26w%3D2048%26q%3D90" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1733164003-individual-test-dashboard-light.png%26w%3D2048%26q%3D90" alt="Individual test dashboard" width="800" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.trunk.io/flaky-tests" rel="noopener noreferrer"&gt;Learn more about Trunk Flaky Tests dashboards.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Need help?
&lt;/h2&gt;

&lt;p&gt;Flaky tests take a combination of well-written tests, taking advantage of your test framework's capabilities, and good tooling to eliminate. If you write e2e tests with Python and Pytest and face flaky tests, know that you're not alone. You don't need to invent your own tools. Trunk can give you the tools needed to tackle flaky tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Autodetect the flaky tests in your build system&lt;/li&gt;
&lt;li&gt;See them in a dashboard across all your repos&lt;/li&gt;
&lt;li&gt;Quarantine tests manually or automatically&lt;/li&gt;
&lt;li&gt;Get detailed stats to target the root cause of the problem&lt;/li&gt;
&lt;li&gt;Get reports weekly, nightly, or instantly sent right to email and Slack&lt;/li&gt;
&lt;li&gt;Intelligently file tickets to the right engineer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://app.trunk.io/login?intent=flaky+tests" rel="noopener noreferrer"&gt;Try Trunk&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>testing</category>
      <category>machinelearning</category>
      <category>productivity</category>
    </item>
    <item>
      <title>What We Learned From Analyzing 20.2 Million CI Jobs In Trunk Flaky Tests - Part 2</title>
      <dc:creator>Vincent Ge</dc:creator>
      <pubDate>Wed, 27 Nov 2024 15:01:17 +0000</pubDate>
      <link>https://dev.to/gewenyu99/what-we-learned-from-analyzing-202-million-ci-jobs-in-trunk-flaky-tests-part-2-1363</link>
      <guid>https://dev.to/gewenyu99/what-we-learned-from-analyzing-202-million-ci-jobs-in-trunk-flaky-tests-part-2-1363</guid>
      <description>&lt;p&gt;This is part two of a blog series about building Trunk Flaky Tests. &lt;a href="https://trunk.io/blog/what-we-learned-from-analyzing-20-2-million-ci-jobs-in-trunk-flaky-tests-part-2" rel="noopener noreferrer"&gt;Part one&lt;/a&gt; covered what we’ve learned about the problem space surrounding flaky tests, like how they impact teams, why they’re prevalent, and why they’re so hard to fix. This part focuses on the technical challenges of building a product to detect, quarantine, and eliminate flaky tests.&lt;/p&gt;

&lt;p&gt;If you’re curious about what we’ve built, you can find more in the &lt;a href="https://trunk.io/blog?post=announcing-trunk-flaky-tests" rel="noopener noreferrer"&gt;Trunk Flaky Tests Public Beta Announcement blog&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Solution
&lt;/h2&gt;

&lt;p&gt;So far, we’ve only discussed the pain points we’ve discovered while working with our beta customers. To recap, flaky tests are prevalent if you write end-to-end tests, but teams underestimate how many of their tests flake and how much time is wasted dealing with Flaky Tests. Fixing flaky tests can cost more engineering resources than ignoring them, but letting flaky tests accumulate will become debilitating. Finding practical ways to deal with flaky tests is the challenge at hand. Let’s talk about some of the technical challenges we’ve faced in building a solution to this problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scale of Flaky Tests
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://www.uber.com/blog/flaky-tests-overhaul/" rel="noopener noreferrer"&gt;Uber’s Go monorepo&lt;/a&gt;, approximately 1,000 out of 600,000 tests were consistently detected as flaky. &lt;a href="https://dropbox.tech/infrastructure/athena-our-automated-build-health-management-system" rel="noopener noreferrer"&gt;Dropbox reported&lt;/a&gt; running over 35,000 builds and millions of automated tests daily, with flaky tests causing significant operational overhead. &lt;a href="https://testing.googleblog.com/2017/04/where-do-our-flaky-tests-come-from.html" rel="noopener noreferrer"&gt;At Google&lt;/a&gt;, out of 4.2 million tests run on their CI system, about 63,000 (1.5%) had a flaky run over a week. More strikingly, approximately 16% of all their tests exhibit flakiness. Over our private beta, we’ve also identified &lt;strong&gt;2.4K&lt;/strong&gt; tests that we labeled flaky. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenges That Come With Scale
&lt;/h2&gt;

&lt;p&gt;Production test suites can be huge. They’re run hundreds of times daily on hundreds of PRs, making it challenging to process all that test data to detect flaky tests. Over our private beta, we &lt;strong&gt;processed&lt;/strong&gt; 20.2 million uploads. This means we received 20.2 million uploads of test results from different CI workflows. Each upload can contain the results of one or many test suites worth of results.&lt;/p&gt;

&lt;p&gt;Even with a limited number of private beta customers, Flaky Tests processes 40 times more data than all of Trunk’s other product offerings &lt;strong&gt;combined&lt;/strong&gt;. We see thousands of times more data for specific types of tables in our database. The volume of data is a challenge in its own right and difficult to query. Creating summary metrics for each test and overall summary metrics across thousands of runs of &lt;strong&gt;tens of thousands of tests&lt;/strong&gt; is very time-consuming.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1731372752-app-trunk-staging-io_totally-real-saas_flaky-tests_repo-gewenyu99_real-saas-app-4.png%26w%3D1200%26q%3D90" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1731372752-app-trunk-staging-io_totally-real-saas_flaky-tests_repo-gewenyu99_real-saas-app-4.png%26w%3D1200%26q%3D90" width="800" height="102"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Single uploads could contain thousands of tests from a CI job. This data needs to be processed, transformed, and labeled to be displayed on various parts of the Flaky Test dashboard. For example, individual test runs need to have their failure reason summarized by an LLM to be displayed in the test details tab. They also need to be labeled with the correct status, such as if they’re healthy, broken, flaky, and if they’ve been rerun on the same PR.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1731372804-app-trunk-staging-io_totally-real-saas_flaky-tests_repo-gewenyu99-2freal-saas-app-repometricscharttype-repo_metrics_trends_quarantined_runs-intervaldays-14.png%26w%3D1200%26q%3D90" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1731372804-app-trunk-staging-io_totally-real-saas_flaky-tests_repo-gewenyu99-2freal-saas-app-repometricscharttype-repo_metrics_trends_quarantined_runs-intervaldays-14.png%26w%3D1200%26q%3D90" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The practical implication is that we can’t naively query and process the data at the time of request because it would take minutes, which is unacceptable. The metrics need to be precomputed in a timely manner. No one wants to wait more than a few minutes for a &lt;a href="https://docs.trunk.io/flaky-tests/github-pull-request-comments" rel="noopener noreferrer"&gt;summary of a PR&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1731372542-github-com_metabase_metabase_pull_49838-1-1-1.png%26w%3D1200%26q%3D90" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1731372542-github-com_metabase_metabase_pull_49838-1-1-1.png%26w%3D1200%26q%3D90" width="800" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s also not trivial to precompute and cache metrics. Different metrics take a different amount of time to compute. If one metric updates faster than another, you’ll see &lt;strong&gt;conflicting numbers on different pieces of UI&lt;/strong&gt;, which is unacceptable. It turns out that extracting useful metrics from a bunch of test runs and providing them quickly enough to be useful is already a challenge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Displaying Tests Results
&lt;/h2&gt;

&lt;p&gt;Another interesting challenge is displaying the thousands of tests, their results, their health status, whether they have an attached ticket/issue, and whether they’ve been quarantined. A lot of information needs to be displayed differently if you’re a team lead keeping track of overall test health or a contributor debugging flaky tests on a PR. After revisions with our beta partners, we landed on three key views for different workflows.&lt;/p&gt;

&lt;p&gt;The first screen is the overview dashboard, which shows the overall health of your tests across many CI jobs. You’ll see how many tests are flaky and broken, and you’ll see time-series data on the trends in your repo. This view focuses on helping you understand the overall impact of flaky tests on your repo and finding the highest-impact tests to tackle next.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1731372620-dashboard-test-list-light-1.png%26w%3D1200%26q%3D90" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1731372620-dashboard-test-list-light-1.png%26w%3D1200%26q%3D90" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you click on a single flaky test, it opens another screen that focuses on helping you debug an individual test. You’ll see a report of its results over the last seven days and the unique ways that the test has failed. You can see when the test started failing and on which branches. This helps you understand what might have caused the test to become flaky and how it fails when it’s flaky, and ultimately helps you debug the test.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1731372666-app-trunk-staging-io_totally-real-saas_flaky-tests_repo-gewenyu99-2freal-saas-app-repometricscharttype-repo_metrics_trends_quarantined_runs-1.png%26w%3D1200%26q%3D90" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1731372666-app-trunk-staging-io_totally-real-saas_flaky-tests_repo-gewenyu99-2freal-saas-app-repometricscharttype-repo_metrics_trends_quarantined_runs-1.png%26w%3D1200%26q%3D90" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On this same screen, you can click through the stack trace of different test runs with a similar failure reason to help debug flaky tests. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1731372689-app-trunk-staging-io_totally-real-saas_flaky-tests_test_6675ee1f-49c5-5400-a6e1-96292a9307ec_status_repo-gewenyu99-2freal-saas-app-intervaldays-14-5.png%26w%3D1200%26q%3D90" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1731372689-app-trunk-staging-io_totally-real-saas_flaky-tests_test_6675ee1f-49c5-5400-a6e1-96292a9307ec_status_repo-gewenyu99-2freal-saas-app-intervaldays-14-5.png%26w%3D1200%26q%3D90" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The third screen is a &lt;a href="https://docs.trunk.io/flaky-tests/github-pull-request-comments" rel="noopener noreferrer"&gt;PR Test Summary&lt;/a&gt;, which shows an aggregated report for all test results from a single PR. This screen slices the data differently so engineers working on a single PR can quickly scan through failures and verify the results. This screen shows if a test failure is a known flaky test, a known broken test, if it’s quarantined, or if this PR introduces the failure. This helps engineers quickly identify the failures they need to address.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1732136998-github-comment-light-1.png%26w%3D1200%26q%3D90" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftrunk.io%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fwww.datocms-assets.com%252F98527%252F1732136998-github-comment-light-1.png%26w%3D1200%26q%3D90" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These screens don’t come from the requirements or feedback of a single customer, everyone has a different set of information that they intuitively believe to be important. Through the beta program, by pooling common feedback from different customers, we think these three different screens manage to condense the massive amount of information needed to understand flaky tests into a digestible format. It fits a variety of use cases and scales well even if you’ve got tens of thousands of tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Quality
&lt;/h2&gt;

&lt;p&gt;There isn’t a &lt;em&gt;single shared standard output format&lt;/em&gt; for test results. While most test runners produce JUnit XML, that is still a loose standard. You can't produce high-quality metrics if you don’t get high-quality data uploads.&lt;/p&gt;

&lt;p&gt;When we ingested data from our customers, we saw random test names, automatic reruns reported as duplicates or overriding the original results, no test file paths, and all kinds of weird data forms. Handling and sanitizing all of this data is challenging. It felt like a game of constant whack-a-mole: We figure out a way to handle one type of data anomaly and out comes another.&lt;/p&gt;

&lt;p&gt;Detecting the inconsistencies and problems in a customer’s uploaded data, prompting them with the proper follow-up steps, and sanitizing the rest is challenging and time-consuming. While we can justify the cost of handling new logic for a new language or test runner, we can’t imagine the pain of building a similar in-house solution, especially if you’ve got a diverse testing stack with legacy code. &lt;/p&gt;

&lt;h2&gt;
  
  
  Detection Rules
&lt;/h2&gt;

&lt;p&gt;What is a flaky test? It’s a debatable and nuanced topic that begins to feel more like a philosophical question than an engineering one when scrutinized. A test can be non-deterministic in a variety of ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How frequently does it have to fail?&lt;/li&gt;
&lt;li&gt;Is a timeout failure the same as a DB connection failed failure?&lt;/li&gt;
&lt;li&gt;Should a failure on a PR treated the same as on the main branch?&lt;/li&gt;
&lt;li&gt;Should the window for detecting a test’s health be N number of runs or a specific time window?&lt;/li&gt;
&lt;li&gt;How do we adjust detection for repos that see 10 test runs vs 1000 test runs?&lt;/li&gt;
&lt;li&gt;How do we determine if a test is healthy again after a fix?&lt;/li&gt;
&lt;li&gt;What about edge cases of tests that fail more than they pass? What about tests that fail once every 1000 runs?&lt;/li&gt;
&lt;li&gt;What if tests never run on &lt;code&gt;main&lt;/code&gt; or another protected branch?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are many signals to consider when detecting Flaky Tests; the answers here aren’t black and white. While detection can never be perfect and never needs to be perfect, it’s possible to hit a maximum for the vast majority of projects and fine-tune for the outliers. Even during a limited private beta, having access to many diverse technology stacks and millions of test runs gives Trunk Flaky Tests a unique advantage in refining our algorithm continuously. &lt;/p&gt;

&lt;p&gt;The private beta customers have already helped us find a number of edge cases that have helped us detect flaky tests more accurately. Our partnership with partners during our private beta and our new public beta program will hopefully allow us to further improve this process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quarantining Tests
&lt;/h2&gt;

&lt;p&gt;Quarantining lets you isolate failures for known flaky tests so they don't fail your CI jobs while continuing to run them. Quarantine looks for known flaky tests and determines which tests should be isolated at runtime so you can avoid code changes usually required to disable flaky tests. This is better than disabling tests because it doesn’t require commenting out tests, which will likely never be revisited. After all, disabled tests produce no noise. It also automatically allows for “un-quarantining” tests if we detect that it’s healthy after a fix. The bottom line is that you’re less likely to lose tests when quarantined.&lt;/p&gt;

&lt;p&gt;Quarantining isn’t as challenging to implement as it is tedious. To implement quarantining, you first need a detection engine for flaky tests. Then, you need to serve that information through some API that a script can fetch in CI jobs to see if a test is known to be flaky. That script needs to run and collect the results of tests (recall the non-standard output formats), compare these results with the API response, and then override the exit code if all failures are quarantined. It must handle overrides for specific tests that should always be quarantined or never quarantined. And that script must work on whatever platform and environment you choose to run your CI jobs on. Finally, you must communicate what’s quarantined in each CI job and track quarantined test results. None of this is very sophisticated, but getting all the domino pieces to fall into place nicely is hard. &lt;/p&gt;

&lt;h2&gt;
  
  
  End-to-End UX
&lt;/h2&gt;

&lt;p&gt;How do you manage flaky tests end-to-end? As discussed in Part 1 of this blog, we don’t see a consensus on a workflow online or among our beta partners. So, if a convention doesn’t exist, we must propose one ourselves. Here’s what we build with the help of our closed-beta customers.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/F2lyu9s-rxs"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;We don’t think flaky tests are a realistic or practical problem for any tooling team to tackle in-house. The problem is nuanced, and there are no conventions for dealing with it effectively. Our private beta customers allowed us to facilitate much-needed discourse around this problem. By having diverse teams and CI setups to test our solutions and pool feedback and ideas, we can help everyone reduce the cost of trial and error when dealing with flaky tests.&lt;/p&gt;

&lt;p&gt;We’re opening Trunk Flaky Tests for public beta so you can help us build a more effective solution for all. The sooner we stop suffering in isolation, the sooner we’ll end flaky tests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.trunk.io/" rel="noopener noreferrer"&gt;Join the Trunk Flaky Tests Public Beta&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>tooling</category>
      <category>productivity</category>
      <category>devops</category>
    </item>
    <item>
      <title>What We Learned From Analyzing 20.2 Million CI Jobs In Trunk Flaky Tests - Part 1</title>
      <dc:creator>Vincent Ge</dc:creator>
      <pubDate>Mon, 18 Nov 2024 15:58:59 +0000</pubDate>
      <link>https://dev.to/gewenyu99/what-we-learned-from-analyzing-202-million-ci-jobs-in-trunk-flaky-tests-part-1-3kj6</link>
      <guid>https://dev.to/gewenyu99/what-we-learned-from-analyzing-202-million-ci-jobs-in-trunk-flaky-tests-part-1-3kj6</guid>
      <description>&lt;p&gt;If you encounter problems with CI stability, it’s probably due to flaky tests. This has proven to be a recurring theme in our conversations with our CI Analytics and Merge Queue users. So, continuing with our goal to eliminate problems that engineers hate, we embarked on a journey to help developers eliminate flaky tests. &lt;/p&gt;

&lt;p&gt;Flaky tests are ubiquitous in teams with automated integration and end-to-end tests, where developers suffer in silence. They’re the biggest challenge to CI stability and a developer pain we’re looking to solve. Even the best teams like &lt;a href="https://www.google.com/search?q=site%3Atesting.googleblog.com%20flaky" rel="noopener noreferrer"&gt;Google&lt;/a&gt;, &lt;a href="https://engineering.fb.com/2020/12/10/developer-tools/probabilistic-flakiness/" rel="noopener noreferrer"&gt;Meta&lt;/a&gt;, &lt;a href="https://engineering.atspotify.com/2019/11/test-flakiness-methods-for-identifying-and-dealing-with-flaky-tests/" rel="noopener noreferrer"&gt;Spotify&lt;/a&gt;, and &lt;a href="https://www.uber.com/blog/flaky-tests-overhaul/" rel="noopener noreferrer"&gt;Uber&lt;/a&gt; waste countless developer hours due to flaky tests, and wasted engineering hours are expensive.&lt;/p&gt;

&lt;p&gt;It’s easy to dismiss flaky tests as a “skill issue” and blame bad test code. But the deeper we dug into test flakiness, the more it looked like a dark, confusing rabbit hole. With our closed-beta partners, we analyzed test results uploaded from 20.2 million CI jobs. We learned that addressing flaky tests in practice requires nuance, you can't just fix all of them because it's impractical. You also can't ignore them because they poison your trust in your test results and slow you down.&lt;/p&gt;

&lt;p&gt;While building Flaky Tests, we’ve learned and suffered, and we can’t wait to share our discoveries with you. Both technical and non-technical. If any of what you read resonates with you or you’re also down the rabbit hole with us looking for a better way to tackle flaky tests, we’d love your feedback in the &lt;a href="https://trunk.io/flaky-tests" rel="noopener noreferrer"&gt;Trunk Flaky Tests Public Beta&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s in Part 1?
&lt;/h2&gt;

&lt;p&gt;This blog is divided into two parts. Part one (what you’re reading) focuses on the problem space. It discusses what we learned about flaky tests, how they impact teams, why they’re prevalent, and why they’re so hard to fix. Part two covers the challenges of building a tool for flaky tests. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/gewenyu99/what-we-learned-from-analyzing-202-million-ci-jobs-in-trunk-flaky-tests-part-2-1363"&gt;You can read part 2 here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  You Will Always Have Some Flaky Tests
&lt;/h2&gt;

&lt;p&gt;From our research, we observed a phenomenon in which organizations that start writing more tests and more realistic tests see a sharp increase in the flakiness of their tests. &lt;/p&gt;

&lt;p&gt;Roy Williams from Meta described the problem as that with a growing number of tests, they hit a “knee” or “hockey stick growth” with flaky tests detected in their code base. Jeff Listfield at Google described in &lt;a href="https://testing.googleblog.com/2017/04/where-do-our-flaky-tests-come-from.html" rel="noopener noreferrer"&gt;one blog&lt;/a&gt; that “over the course of a week, 0.5% of our small tests were flaky, 1.6% of our medium tests were flaky, and 14% of our large tests were flaky.” When you consider that Google has over 4.2 million tests, this is a huge number of flaky tests.&lt;/p&gt;

&lt;p&gt;A significant contributor to this phenomenon is that your tests become more complex when you write more realistic tests, like integration tests and end-to-end tests. In an end-to-end test, there can be hundreds of moving parts. Your tests can be flaky for various reasons, like running out of RAM, a service container not spinning up in time, or misconfigured DNS. &lt;/p&gt;

&lt;p&gt;Testing is about confidence, and you won't be confident if you don’t have any tests that span your whole system. Dylan Frankland, a lead engineer here at Trunk, puts it like this:&lt;/p&gt;

&lt;p&gt;“You can try to make your tests completely un-flakable. But you will have very low confidence in your code. You can mock out every piece and never have a flake, but at the end of the day, then, you're testing mocks, not real code.” &lt;/p&gt;

&lt;p&gt;With complexity, writing tests that never flake is practically inevitable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Suffering in Silence
&lt;/h2&gt;

&lt;p&gt;Software engineers often suffer silently. Tenaciousness, independence, and resourcefulness are often considered standout qualities of great software engineers. However, when tackling flaky tests, these qualities work against them by masking the problem. &lt;/p&gt;

&lt;p&gt;When an engineer encounters a flaky test, they might debug it for 30 minutes, decide that this can’t be related to their change, and rerun the test. This sometimes fixes the problem. Since the problem is not critical, they sweep it under the rug, and no one else learns about it. This lets flaky tests accumulate until rerunning no longer clears failures. &lt;/p&gt;

&lt;p&gt;If you’re like Uber, who in their Go Monorepo reports 1000+ flaky tests, you’ll rarely see PRs without flaky failures. Even if each test only has a 0.1% flake rate, the chances of having at least 1 flaky failure are &lt;code&gt;1 - 0.999^1000&lt;/code&gt; or &lt;code&gt;~63%&lt;/code&gt;. That means 63% of PRs will need at least 1 rerun to clear the flaky failure and distract an engineer to debug the failures. If you let the innocent-looking flaky tests accumulate silently, when you finally notice it, it'll already be a monster that paralyzes your PR velocity.&lt;/p&gt;

&lt;p&gt;We made a calculator to help you better understand how flaky tests could impact your team, and we recommend that you &lt;a href="https://trunk.io/flaky-tests" rel="noopener noreferrer"&gt;experiment with the numbers yourself&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhntcceqjujq3rnwpbbo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhntcceqjujq3rnwpbbo.png" alt="Trunk's impact calculator for flaky tests" width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We had this problem at Trunk, too. Federico, one of our TLMs, noted after adopting dogfooding flaky tests: "We knew we had flaky tests because we were experiencing failures, but the tool revealed the full extent of the issue." It wasn’t until he saw the numbers in the Flaky Tests dashboard that he realized just how many PRs are being blocked by flaky tests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fokyziudvj96fkdxl2nmh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fokyziudvj96fkdxl2nmh.png" alt="Image description" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our beta partners also shared this sentiment. They frequently underestimated the scale of their flaky test problems and were surprised by the data they saw.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s The Impact?
&lt;/h2&gt;

&lt;p&gt;When we interviewed our closed beta partners on the value of eliminating flaky tests, the top response was the number of engineering hours saved. What we intuitively thought would matter the most was that the CI cost saved from not rerunning flaky tests didn’t even come close. We think flaky tests impact engineering velocity in three main ways: time lost from blocked PRs, time lost context switching, and time lost managing flaky tests.&lt;/p&gt;

&lt;p&gt;Time lost from flaky tests blocking PRs is easy to understand. If your CI jobs aren’t passing, you can’t merge a PR. Most repositories have branch protections that prevent PRs from being merged unless all checks/tests are passing. If your tests are very flaky, trying to merge perfectly good code still feels like a dice roll. Combined with long-running tests, blocked PRs become very painful.&lt;/p&gt;

&lt;p&gt;In the study &lt;a&gt;Cost of Interrupted&lt;/a&gt;, research pointed to ~23 minutes of productivity lost per context switch. I wouldn't take this at face value, but if you observe your coworkers in an office, you might find this number close.&lt;/p&gt;

&lt;p&gt;You might see a series of events 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;10:00:00 - INFO - Bob submits a pull request (PR).
10:15:00 - ERROR - CI job fails on Bob's PR.
10:20:00 - INFO - Bob reruns the tests after identifying the failure is unrelated to his changes.
10:25:00 - INFO - Bob takes a break for coffee and begins a new task.
10:45:00 - INFO - Bob remembers to check the test results for his PR.
10:46:00 - SUCCESS - Bob merges his PR.
11:15:00 - ALERT - Bob is paged due to a flaky test failure in the main branch.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm exaggerating, but if your tests are flaky and your team is large, those little distractions add up fast.&lt;/p&gt;

&lt;p&gt;Looping back, we also mentioned a third way flaky tests impact engineering velocity, which is time lost managing flaky tests. This is all the time lost from replying to email threads, Slack messages, GitHub, and comments about which tests are flaky. It’s time lost when creating tickets, maintaining a list of known flaky tests, and updating the list when the test is fixed. Team leads who usually have the most insight into a team’s test health and are responsible for ensuring code standards usually feel this the most. They’re the ones that get pinged, and they manage the tickets.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Importance of Communication
&lt;/h2&gt;

&lt;p&gt;Making it easier to triage flaky tests and communicate them with your team is essential. A lot of the time wasted on Flaky Tests is in the interruptions and duplicated work debugging flaky tests. Each time a flaky failure pops up on your CI jobs, an engineer context switches to debug the test. If the fix is non-trivial and a rerun clears the failure, they’ll give up and move on.&lt;/p&gt;

&lt;p&gt;The problem is that they don’t tell anyone else about this, and the same debugging process is repeated for every engineer who comes across this flaky test. As mentioned before, each context switch also takes time to recover from. If your tests take 20 minutes to run, you’ll be distracted by a notification 20 minutes into the next task you picked up after submitting your PR. That’s right when you enter deep focus.&lt;/p&gt;

&lt;p&gt;Offloading these spontaneous disruptions and turning them into planned work reduces context switching. Communicating who owns which flaky test and if the ticket is being worked on can prevent wasted time from duplicated efforts to debug the same flaky test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing Every Flaky Test Isn’t The Solution
&lt;/h2&gt;

&lt;p&gt;Flaky tests can be caused by flaky test code, flaky infrastructure, or flaky production code. This last part is why many teams intuitively aim to fix all flaky tests.&lt;/p&gt;

&lt;p&gt;Although we’ve seen some of our beta customers adopt on-call rotations to swat flaky tests as they appear, most of our other beta customers decided against fixing all their flaky tests. Most teams opt against fixing all their flaky tests because it’s impractical.&lt;/p&gt;

&lt;p&gt;Dropbox tried to offload the burden to an engineering rotation to fix broken tests, and they noted, “The operational load of that rotation became too high, and we distributed the responsibility to multiple teams, which all felt the burden of managing build health.” At some point, the cure became worse than the disease.&lt;/p&gt;

&lt;p&gt;Addressing flaky tests is intended to speed up development and reduce pain. This means fewer blocked PRs, fewer developer hours wasted debugging flaky failures, and fewer wasted CI resources. If this is the goal, then not every flaky test is worth fixing. You can’t spend more engineering hours fixing flaky tests than the time lost when ignoring them; the investment has to be economical.&lt;/p&gt;

&lt;p&gt;Some flaky failures appear once every few weeks and disappear again, while others might block every other CI job. Fixing the flaky tests that block most developers first and reducing the felt impact of other flaky tests yields the most bang for the buck. Trunk helps you make more cost-effective decisions by letting you sort tests by PRs impacted. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7djldadmnyw5nkk5wagj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7djldadmnyw5nkk5wagj.png" alt="Image description" width="800" height="295"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the difficult-to-fix and lower-impact flaky tests, you still want to reduce their impact even if fixing them is impractical. The sweet spot here seems to be &lt;a href="https://docs.trunk.io/flaky-tests/quarantining" rel="noopener noreferrer"&gt;quarantining&lt;/a&gt; flaky tests so they don’t block PRs. Quarantining lets you isolate test results from flaky tests at run time. These quarantined tests still run and report results but don’t block PRs. This also ensures that no tests are disabled because disabled tests are rarely revisited and disabled. They just rot, and you lose test coverage. When you fix a flaky test, this continued tracking also clarifies if that fix works.&lt;/p&gt;

&lt;p&gt;Another important observation is that we sometimes see flaky tests because of the underlying infrastructure, which is often outside the teams’ control. For tests like that, there’s very little you can do to fix the test. These could be special tests that span many services, require specific hardware, or rely on external APIs. A “spillway” if you will, where failures due to reasons outside a team’s practical control get handled are useful in their own right.&lt;/p&gt;

&lt;p&gt;What's more, is that &lt;a href="https://dl.acm.org/doi/abs/10.1145/3377811.3381749" rel="noopener noreferrer"&gt;studies&lt;/a&gt; have shown that "fixes" submitted by developers to fix flaky tests often don't work anyway, which is more of a reason to have a proper monitoring solution for flaky tests. You need to be able to see that a fix actually worked on your flaky test before marking it healthy again, otherwise, it just ruins your developers' trust in your tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleaning Under The Bed
&lt;/h2&gt;

&lt;p&gt;One of the main concerns about quarantining tests is that they will be forgotten after the friction is gone. Quarantining fixes the issue of the “boy who cried wolf,” where teams ignored their CI results because the tests were so flaky they lost trust in tests. Quarantine can potentially do the opposite, where flaky tests cause too little friction, and people still ignore them.&lt;/p&gt;

&lt;p&gt;To prevent this, we must ensure good reporting and insight to prevent erosion in test coverage over time. We’re currently trying a few ideas. One sees time series data on the number of quarantined test runs over time. You want to see this number go down, not up. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcuq753su53gl4g9ppj73.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcuq753su53gl4g9ppj73.png" alt="Image description" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another is periodic reports. These should surface overall trends in test health, like if your tests are becoming flakier, point to the new flaky tests, and show if the top flaky tests have changed. &lt;/p&gt;

&lt;h2&gt;
  
  
  What’s In The Next Part
&lt;/h2&gt;

&lt;p&gt;To recap, flaky tests are prevalent if you write end-to-end tests, but teams underestimate how many of their tests flake and how much time is wasted dealing with Flaky Tests. Flaky Tests are annoying enough to have a substantial impact on productivity, but the fix for many teams is more expensive than to keep rerunning tests. The challenge, then, is to create a workflow that reduces the impact of flaky tests without making the cure worse than the disease.&lt;/p&gt;

&lt;p&gt;Read part two for more about the challenges we faced building a solution to detect, quarantine, and eliminate flaky tests.&lt;/p&gt;

&lt;p&gt;Try Trunk Flaky Tests&lt;/p&gt;

&lt;p&gt;We don’t think flaky tests are a realistic or practical problem to tackle in-house. For this exact reason, we’re opening Trunk Flaky Tests for public beta so you can help us build a more effective solution for all. The sooner we stop suffering in isolation and begin pooling our feedback into a single solution, the sooner we’ll end flaky tests.&lt;/p&gt;

&lt;p&gt;Join the &lt;a href="https://app.trunk.io/" rel="noopener noreferrer"&gt;Trunk Flaky Tests Public Beta&lt;/a&gt; and &lt;a href="https://dev.to/gewenyu99/what-we-learned-from-analyzing-202-million-ci-jobs-in-trunk-flaky-tests-part-2-1363"&gt;read part two of this blog here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>tooling</category>
      <category>productivity</category>
      <category>devops</category>
    </item>
    <item>
      <title>Convincing non-technical leadership to invest in DevEx.</title>
      <dc:creator>Vincent Ge</dc:creator>
      <pubDate>Mon, 26 Aug 2024 17:51:35 +0000</pubDate>
      <link>https://dev.to/gewenyu99/convincing-non-technical-leadership-to-invest-in-devex-51n0</link>
      <guid>https://dev.to/gewenyu99/convincing-non-technical-leadership-to-invest-in-devex-51n0</guid>
      <description>&lt;p&gt;Working as a DevRel for a &lt;a href="https://trunk.io/" rel="noopener noreferrer"&gt;DevTools company&lt;/a&gt;, I sometimes find myself advocating not just for our users/our company, I often do advocacy strategy &lt;strong&gt;for our users toward their leadership&lt;/strong&gt; because they need to convince their org on why they're buying in.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh7dpv0fxafak7psl5qje.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh7dpv0fxafak7psl5qje.jpg" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's hard if their non-technical leadership cares about only MRR and ARR numbers (which is most of them).&lt;/p&gt;

&lt;h2&gt;
  
  
  Wait, some teams don't run tests?
&lt;/h2&gt;

&lt;p&gt;Whether we're on Reddit, Tech Twitter, or DailyDev, we share (or pretend to share) an understanding of striving for cleanly written, well-tested, and well-maintained code bases.&lt;/p&gt;

&lt;p&gt;The reality, however, is that many engineering orgs struggle to do the basics. &lt;/p&gt;

&lt;p&gt;I've heard something similar to this several times:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We don't need to keep tests from failing in main. Some individuals on my team don't even believe in writing tests. I don't think they'll buy into a merge queue.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Our non-technical leadership doesn't like it when we spend time working on code or tools that don't impact revenue. Linting and formatting falls into this category.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or even:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;100% test coverage is not necessary, some parts of the code is so simple, I don't need to test it to know if it works.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These people will often make it to engineering leadership in an org for being &lt;strong&gt;pragmatic&lt;/strong&gt;. EW...&lt;/p&gt;

&lt;h2&gt;
  
  
  But "I" know better!
&lt;/h2&gt;

&lt;p&gt;Now we know better. We know that full test coverage helps us &lt;strong&gt;make changes faster and more confidently&lt;/strong&gt; because we have guardrails that prevent us from breaking other people's code. We know that &lt;strong&gt;untested code is code that doesn't work&lt;/strong&gt;. We know we need linted and formatted code so everyone can &lt;strong&gt;focus on problem-solving instead of arguing&lt;/strong&gt; about how many tabs to use. We know that linters can help &lt;strong&gt;catch anti-patterns&lt;/strong&gt; that can impact &lt;strong&gt;security&lt;/strong&gt; and &lt;strong&gt;performance&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I know that you know better. How do you ensure that leadership understands why these things are important?&lt;/p&gt;

&lt;p&gt;And no, saying that they're incompetent and engineers should lead your org isn't a valid answer, most of the time. &lt;/p&gt;

&lt;h2&gt;
  
  
  How to sell to non-engineers
&lt;/h2&gt;

&lt;p&gt;Our concerns about DevEx, code quality, testing, and well-architected code are all valid. Where it goes wrong is how we sell this to leadership.&lt;/p&gt;

&lt;p&gt;The problem is that we're too &lt;strong&gt;abstract&lt;/strong&gt; in how we sell it.&lt;/p&gt;

&lt;p&gt;We often focus on saying &lt;strong&gt;this is the standard&lt;/strong&gt;, &lt;strong&gt;this is better practice&lt;/strong&gt;, or &lt;strong&gt;this is what other top engineers do&lt;/strong&gt;. This is abstract. Engineers take these "standard practices" as truth because someone else (and maybe even you!) has felt the pain hundreds of times and learned to avoid it. Your non-technical leadership and even some technical leaders who haven't wet their toes in years will not relate easily.&lt;/p&gt;

&lt;p&gt;Instead, this is what you need to focus on. This is the story you need to tell.&lt;/p&gt;

&lt;h3&gt;
  
  
  Focus on impact
&lt;/h3&gt;

&lt;p&gt;Poor testing has a cost. If you change code and there's insufficient testing to ensure you won't break someone else's code, it will resurface unexpectedly during development/code review or in production.&lt;/p&gt;

&lt;p&gt;If your code base has poor coverage, this has probably already happened. Collect these &lt;strong&gt;anecdotes&lt;/strong&gt;, or create them by doing some mocks. Create some innocent-looking code change that will have an unexpected impact to use as an example.&lt;/p&gt;

&lt;p&gt;Same with code quality and linters/formatters. If you can't find stories, commit a change with tabs instead of spaces, 4 wide tabs instead of 2 wide, add your line breaks in the wrong places, &lt;strong&gt;argue with your coworkers for a few hours&lt;/strong&gt;, and save the email exchange or Slack thread.&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%2Fres.cloudinary.com%2Fdaily-now%2Fimage%2Fupload%2Fs--R6N0r9bL--%2Ff_auto%2Fv1724693369%2Fugc%2Fcontent_13e88df5-90fd-428c-93d5-5da1d06e6926" 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%2Fres.cloudinary.com%2Fdaily-now%2Fimage%2Fupload%2Fs--R6N0r9bL--%2Ff_auto%2Fv1724693369%2Fugc%2Fcontent_13e88df5-90fd-428c-93d5-5da1d06e6926" alt="coding-style"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Quantify the impact
&lt;/h3&gt;

&lt;p&gt;How much does it cost to break the main? How much does it cost to argue about code quality? How much does it cost to take down your production application?&lt;/p&gt;

&lt;p&gt;Remember how much you and your teammates' time cost? What we often fail to illustrate is how fucking expensive it is for a dev team to argue about where the &lt;code&gt;{&lt;/code&gt; should go or how an innocent-looking change is breaking tests in main.&lt;/p&gt;

&lt;p&gt;While your leadership won't understand pain, they understand &lt;code&gt;developer_hours == $$$&lt;/code&gt;. Now you have your anecdotes, make a chart of how much it costs to manually retest things every code review, or argue about code format.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every PR has an 80% chance of containing a formatting issue; 4 engineers argue for an hour 20% of the time.&lt;/li&gt;
&lt;li&gt;Every PR has a 20% chance of breaking something unexpectedly; 50% of the time, it's caught, but it costs 2 engineers 4 hours on average to find the issue. The other 50% of the time it takes down prod for an hour.&lt;/li&gt;
&lt;li&gt;We put in 10 PRs per week.&lt;/li&gt;
&lt;li&gt;Engineers make $75 an hour.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now do the math... Actually I feel lazy, here's what ChatGPT says.&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%2Fres.cloudinary.com%2Fdaily-now%2Fimage%2Fupload%2Fs--0sCATzgL--%2Ff_auto%2Fv1724693886%2Fugc%2Fcontent_681f316a-960a-43c1-b8aa-e02a4dbeefcb" 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%2Fres.cloudinary.com%2Fdaily-now%2Fimage%2Fupload%2Fs--0sCATzgL--%2Ff_auto%2Fv1724693886%2Fugc%2Fcontent_681f316a-960a-43c1-b8aa-e02a4dbeefcb" alt="Screenshot 2024-08-26 at 1.37.58 PM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's Claude:&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%2Fres.cloudinary.com%2Fdaily-now%2Fimage%2Fupload%2Fs--D4y2_33H--%2Ff_auto%2Fv1724693973%2Fugc%2Fcontent_1e37af2c-3ee9-4acc-b246-7e615163fbd5" 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%2Fres.cloudinary.com%2Fdaily-now%2Fimage%2Fupload%2Fs--D4y2_33H--%2Ff_auto%2Fv1724693973%2Fugc%2Fcontent_1e37af2c-3ee9-4acc-b246-7e615163fbd5" alt="Screenshot 2024-08-26 at 1.39.19 PM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wow they disagree, but regardless, that's a big number, probably easily 10-20x the cost of a tool, and in a year, enough money to justify hiring an intern to do nothing but address these issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Restricting scope
&lt;/h3&gt;

&lt;p&gt;Now, pitch your fix. How much time, what tools, how long, and what's the ROI. Focus on 1 high-impact issue that has a clear scope. Focus on how long you think it will take and what you get back. You're looking to achieve ~10X ROI in a year for a pitch that they can't resist (and it's not hard).&lt;/p&gt;

&lt;h2&gt;
  
  
  Time to practice
&lt;/h2&gt;

&lt;p&gt;This stuff takes practice and is often the differentiator between staff engineer/tech leads and your run-of-the-mill dev. You should try these tricks if you're not yet a staff engineer/tech lead. It shows initiative and has 0 risk for you (because it's not your job anyway).&lt;/p&gt;

&lt;h2&gt;
  
  
  A word from our sponsor
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://trunk.io/" rel="noopener noreferrer"&gt;Trunk&lt;/a&gt; makes several dev tools that you can use to refine and practice your pitch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.trunk.io/code-quality" rel="noopener noreferrer"&gt;Formatting and linting in big repos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.trunk.io/merge-queue" rel="noopener noreferrer"&gt;Prevent conflicting changes from breaking main at scale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://trunk.io/flaky-tests" rel="noopener noreferrer"&gt;Find and fix your flaky tests&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you'd like to get some advice on how to write your pitch, &lt;a href="https://slack.trunk.io/" rel="noopener noreferrer"&gt;chat with us on Slack&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devex</category>
      <category>tooling</category>
      <category>testing</category>
      <category>leadership</category>
    </item>
    <item>
      <title>Regex, the good bits.</title>
      <dc:creator>Vincent Ge</dc:creator>
      <pubDate>Tue, 25 Jun 2024 04:03:54 +0000</pubDate>
      <link>https://dev.to/gewenyu99/regex-the-good-bits-4l2o</link>
      <guid>https://dev.to/gewenyu99/regex-the-good-bits-4l2o</guid>
      <description>&lt;p&gt;There are two types of developers: those who fear regex because they don't understand it and those who abuse regex to flex on their millennial teammates.&lt;/p&gt;

&lt;p&gt;The purpose of this blog is to get you somewhere in between. Know the bits that will be super useful without being dangerous. &lt;/p&gt;

&lt;h2&gt;
  
  
  Wait, regex can be dangerous?
&lt;/h2&gt;

&lt;p&gt;Regex can do some spectacular things. You can write entire programs in regex. But just because you can, doesn't mean you should. Imagine a giant regex pattern uses all the powerful bits of regex, like recursive patterns, conditional patterns, look ahead and look behinds, and introducing side-effects with a replace. &lt;/p&gt;

&lt;p&gt;I mean look at this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(function(a,b){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))window.location=b})(navigator.userAgent||navigator.vendor||window.opera,'http://detectmobilebrowser.com/mobile');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was a &lt;a href="http://detectmobilebrowsers.com/mobile#google_vignette" rel="noopener noreferrer"&gt;somewhat commonly used pattern to detect mobile browsers&lt;/a&gt; at some point.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExd2s0NjBibm94bXcyZWpjcDR0b3E1eWR2N3Jrdzc1YTgxam9xdGZrYiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/VkSC9qXzXJd7aIth9I/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExd2s0NjBibm94bXcyZWpjcDR0b3E1eWR2N3Jrdzc1YTgxam9xdGZrYiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/VkSC9qXzXJd7aIth9I/giphy.gif" alt="burn it with fire" width="600" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My problem with regex is this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Regex syntax is extremely concise, which means extreme mental-overhead to read and write.&lt;/li&gt;
&lt;li&gt;Regex has lots of exceptions. It's grammar and rules are inconsistent at best.&lt;/li&gt;
&lt;li&gt;Really hard to split into multiple lines and not endup with a huge cursed string.&lt;/li&gt;
&lt;li&gt;Updating regex to accept new behavior, reuse bits of the logic is hard.&lt;/li&gt;
&lt;li&gt;When it gets large enough, everyone's afraid to touch it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You will end up with impossible to read and maintain code if you go too far with regex.&lt;/p&gt;

&lt;h2&gt;
  
  
  On to the good bits
&lt;/h2&gt;

&lt;p&gt;At its core, regex is a powerful way to search and match text based on rules, and extract information into variables. It &lt;strong&gt;can&lt;/strong&gt; be used to manipulate string, but I'm going to avoid this. Most people do not expect regex to have side effects.&lt;/p&gt;

&lt;p&gt;Stuff like pulling out a html tag with certain classnames, &lt;a href="https://stackoverflow.com/questions/8358084/regular-expression-to-reformat-a-us-phone-number-in-javascript" rel="noopener noreferrer"&gt;formatting phone numbers&lt;/a&gt;, and log parsing are great examples of good places to use regex.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic patterns
&lt;/h2&gt;

&lt;p&gt;Take this example of using regex in JS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello world&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is a short! message that says &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Hello world&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I love regular expressions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// this is the pattern&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// the pattern can be used to "test" for matches&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The regular expression pattern &lt;code&gt;ello&lt;/code&gt; used in &lt;code&gt;re.test()&lt;/code&gt; will match any string containing the pattern as a substring. This is the simplest type of pattern.&lt;/p&gt;

&lt;p&gt;It will match the following lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ 'Hello world', 'This is a short! message that says "Hello world"' ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These patterns are by default case sensitive. You can define case in-sensitive with the option &lt;code&gt;new RegExp("ello", "i");&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start and end of text
&lt;/h3&gt;

&lt;p&gt;Regular expressions has "meta characters" that define logical rules for your pattern to match against.&lt;/p&gt;

&lt;p&gt;The character &lt;code&gt;^&lt;/code&gt; means beginning of text and &lt;code&gt;$&lt;/code&gt; means end of string.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This will match "Hello, world!" but not "Message: Hello, world!".&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^Hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// This will match "Hello, world" but not "Hello, world!"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;world$&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Match one of variations
&lt;/h3&gt;

&lt;p&gt;Sometimes you want to match variations of a similar pattern. In the most basic cases, variations of words like &lt;code&gt;fine&lt;/code&gt;, &lt;code&gt;pine&lt;/code&gt;, and &lt;code&gt;line&lt;/code&gt;. In these cases, you can define a group of options in brackets like this: &lt;code&gt;[]&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fine&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pine&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;line!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// This will match all the words above.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[fpl]ine&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use ranges of ascii characters in these any-of groups like this &lt;code&gt;[a-zA-Z0-9]&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1ine&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pine&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zine!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// still match all the words.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[a-zA-Z0-9]ine&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An alternative approach is to use &lt;code&gt;|&lt;/code&gt; which represents a logical or to match alternatives.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;color&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;colour&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;color|colour&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Wildcard
&lt;/h3&gt;

&lt;p&gt;Sometimes you don't want to specify options, you want to match every variation imaginable. We can use the &lt;code&gt;.&lt;/code&gt; character to specify a wildcard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;%ine&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;}ine&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;`ine!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.ine&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Repeating patterns
&lt;/h3&gt;

&lt;p&gt;Sometimes we want to match a character repeatedly. For example, matching every variation of &lt;code&gt;yeet&lt;/code&gt;, like &lt;code&gt;yeeeeeeeeeet&lt;/code&gt; or &lt;code&gt;yeeeeeeeeeeeeeeeeeeeeeeeet&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can use &lt;code&gt;+&lt;/code&gt; or &lt;code&gt;*&lt;/code&gt;. &lt;code&gt;*&lt;/code&gt; matches the preceding element zero or more times, &lt;code&gt;+&lt;/code&gt;    Matches the preceding element one or more times.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yeet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yeeet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yeeeeeeeeet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// matches [ 'yeeet', 'yeeeeeeeeet' ]&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;re1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yeee+t&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// matches [ 'yeet', 'yeeet', 'yeeeeeeeeet' ]&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;re2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yeee*t&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;re1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;re2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;+&lt;/code&gt; if you want to match the character at least once, use * to mean it's optional but try to match many times if possible.&lt;/p&gt;

&lt;p&gt;A interesting side effect of this is that they can be combined with the wildcard &lt;code&gt;.&lt;/code&gt;. Try &lt;code&gt;.*&lt;/code&gt; and &lt;code&gt;.+&lt;/code&gt; in your patterns, but becareful, &lt;code&gt;.*&lt;/code&gt; will match literally anything which can be very error prone.&lt;/p&gt;

&lt;p&gt;Another useful piece of syntax is &lt;code&gt;{}&lt;/code&gt; which specifies the number of times a character or part of a pattern is repeated. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1011&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;222222&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// matches [ '1011' ]&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^[0-9]{4}$&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Useful macros
&lt;/h3&gt;

&lt;p&gt;There are some metacharacters that behave kinda like macros. These metacharacters are fundamental in constructing regex patterns to match specific text patterns in strings.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metacharacter&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Example Match&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Digit (0-9)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;4&lt;/code&gt;, &lt;code&gt;9&lt;/code&gt;, &lt;code&gt;0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\D&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Non-digit&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;a&lt;/code&gt;, &lt;code&gt;Z&lt;/code&gt;, &lt;code&gt;%&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\w&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Word character&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;a&lt;/code&gt;, &lt;code&gt;A&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;_&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\W&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Non-word character&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;!&lt;/code&gt;, &lt;code&gt;@&lt;/code&gt;, &lt;code&gt;#&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Whitespace&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&lt;/code&gt;, &lt;code&gt;\t&lt;/code&gt;, &lt;code&gt;\n&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\S&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Non-whitespace&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;a&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;%&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Word boundary&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;\bword\b&lt;/code&gt;, &lt;code&gt;\b123\b&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Non-word boundary&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;\Bword\B&lt;/code&gt;, &lt;code&gt;\B123\B&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;\d&lt;/code&gt;, &lt;code&gt;\w&lt;/code&gt;, and &lt;code&gt;\s&lt;/code&gt; are pretty self-explanatory. What I wanna focus on is the &lt;code&gt;\b&lt;/code&gt; and &lt;code&gt;\B&lt;/code&gt; meta characters. These are extremely useful when parsing prose, because they respect natural word boudaries. For example in "hello, boss", hello is a independent word, but it's followed by a &lt;code&gt;,&lt;/code&gt; which means if you naively matched the pattern &lt;code&gt;\shello\s&lt;/code&gt;, the word will be missed. Similarly, matching for &lt;code&gt;hello&lt;/code&gt; naively will also match words like &lt;code&gt;phelloplastics&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello, boss&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;galvanized square steel.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dave has saved up a looooonnnggg time for his new prison-esque house.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// matches words ["hello, boss"]&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;re1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;bhello&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// matches in words ["dave has saved up a looooonnnggg t...]&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;re2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;Bsq&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;B&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;re1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;re2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Extracting values
&lt;/h2&gt;

&lt;p&gt;Capture groups in regex let you extract values with a patter. You define capture groups with &lt;code&gt;(&amp;lt;subpattern&amp;gt;)&lt;/code&gt; and everything matched by the pattern enclosed in &lt;code&gt;()&lt;/code&gt; is returned.&lt;/p&gt;

&lt;p&gt;For example, parsing an email:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example.user123@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;([\w\.&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;@&lt;/span&gt;&lt;span class="se"&gt;([\w\.&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)\.([&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z&lt;/span&gt;&lt;span class="se"&gt;]{2,6})&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invalid email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// the capture group is returned as an array of matches.&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;tld&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Username:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Domain:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Top-Level Domain (TLD):&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tld&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's even cooler is that capture groups can be named for more readable patterns and they can be used to match multiple times. For example, here we can extract multiple emails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example.user123@example.com example.user123@example.com example.user123@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(?&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;username&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;[\w\.&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;@&lt;/span&gt;&lt;span class="se"&gt;(?&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;domain&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;[\w\.&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)\.(?&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;tld&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z&lt;/span&gt;&lt;span class="se"&gt;]{2,6})&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invalid email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// loop over all matches&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;group&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;tld&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tld&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Username:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Domain:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Top-Level Domain (TLD):&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tld&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wait, you missed this cool thing!
&lt;/h2&gt;

&lt;p&gt;If you already know a lot of regex, great! I know everyone has their favorite little tricks with regex. The point of this post is to remove the &lt;em&gt;fear&lt;/em&gt; many developers feel when they see regex in code. I think the subset of regex introduced in this post give you more than enough to be powerful and literate in regex, but not enough to become abusive.&lt;/p&gt;

&lt;p&gt;Of couse, if you feel like there's something cool others should know that I missed, leave it in the comments!&lt;/p&gt;

&lt;h2&gt;
  
  
  Cool stuff you can do
&lt;/h2&gt;

&lt;p&gt;I've seen engineers do crazy things with grep. At a old job where we wrote realtime operating systems (ancient 30 year-old code bases), I saw entire chunks of our build process written in &lt;code&gt;sed&lt;/code&gt; and &lt;code&gt;awk&lt;/code&gt; which relies heavily on regex.&lt;/p&gt;

&lt;p&gt;Other cool things you can do is write your own linters. I work at &lt;a href="https://x.com/trunkio" rel="noopener noreferrer"&gt;Trunk&lt;/a&gt;, and we make a thing called Trunk Check where you can &lt;a href="https://docs.trunk.io/check/configuration/custom-linters#defining-a-custom-linter" rel="noopener noreferrer"&gt;write grep linters in less than a minute&lt;/a&gt; if you know your regex patterns.&lt;/p&gt;

&lt;p&gt;Play around and share the cool/crazy stuff you make with the internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find me on socials
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://x.com/WenYuGe1" rel="noopener noreferrer"&gt;TwitterX&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://app.daily.dev/squads/trunkio" rel="noopener noreferrer"&gt;daily.dev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>regex</category>
      <category>tooling</category>
      <category>cli</category>
      <category>javascript</category>
    </item>
    <item>
      <title>You're parsing URLs wrong.</title>
      <dc:creator>Vincent Ge</dc:creator>
      <pubDate>Sun, 26 May 2024 00:00:51 +0000</pubDate>
      <link>https://dev.to/gewenyu99/youre-parsing-urls-wrong-1eld</link>
      <guid>https://dev.to/gewenyu99/youre-parsing-urls-wrong-1eld</guid>
      <description>&lt;p&gt;There are somethings that you should never build by yourself. Not because it's difficult, but because it's time consuming and filled with gotchas.&lt;/p&gt;

&lt;p&gt;One of these things is URL parsing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try implementing your own URL parsing
&lt;/h2&gt;

&lt;p&gt;Raise your hand if you've done this before ✋&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resourceId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fullUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: https://example.com/api/123&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But you're not a barbarian, maybe you abstract this into a function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;joinUrlParts&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Example usage:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resourceId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;joinUrlParts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fullUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: https://example.com/api/123&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well, that's certainly better, but this example can break with a single stray &lt;code&gt;/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example usage that breaks:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;api/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resourceId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;joinUrlParts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fullUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: https://example.com/api//123&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So maybe you tried again, you add sanitation to your input.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;joinUrlParts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Trim leading and trailing slashes from each part&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sanitizedParts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;+|&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;+$/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="c1"&gt;// Join the sanitized parts with slashes&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;sanitizedParts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks bullet proof? Right? Not that many edge cases!&lt;/p&gt;

&lt;p&gt;Well, yes, but actually no.&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%2Fi.kym-cdn.com%2Fentries%2Ficons%2Ffacebook%2F000%2F028%2F596%2FdsmGaKWMeHXe9QuJtq_ys30PNfTGnMsRuHuo_MUzGCg.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%2Fi.kym-cdn.com%2Fentries%2Ficons%2Ffacebook%2F000%2F028%2F596%2FdsmGaKWMeHXe9QuJtq_ys30PNfTGnMsRuHuo_MUzGCg.jpg" alt="Well yes but actually no meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You still have to support some interesting use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What about joining &lt;code&gt;https://example.com/cats&lt;/code&gt; and &lt;code&gt;../dogs/corgie&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;This doesn't understand &lt;code&gt;https://example.com/dogs/corgie#origins&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;This doesn't escape characters &lt;code&gt;http://www.example.com/d%C3%A9monstration.html&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;This doesn't accept query parameters &lt;code&gt;https://some.site/?id=123&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;This doesn't let you &lt;strong&gt;parse&lt;/strong&gt; URLs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The list goes on. You &lt;strong&gt;CAN&lt;/strong&gt; implement what &lt;/p&gt;

&lt;h2&gt;
  
  
  But why does this matter?
&lt;/h2&gt;

&lt;p&gt;If you write modern JavaScript, you know that the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/URL" rel="noopener noreferrer"&gt;URL object&lt;/a&gt; exists from JS Web APIs.&lt;/p&gt;

&lt;p&gt;But JavaScript didn't always have a good way to construct and parse URLs built in. The URL object was first included in the &lt;a href="https://262.ecma-international.org/6.0/" rel="noopener noreferrer"&gt;ECMAScript 2015 specs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are still lots of older videos and blogs that parse URLs with all kinds of fancy magic like &lt;a href="https://www.youtube.com/watch?v=ZZH20MO1yP8&amp;amp;ab_channel=HelpVideoGuru" rel="noopener noreferrer"&gt;JavaScript - How to Get URL Parameters - Parse URL Variables&lt;br&gt;
&lt;/a&gt; and some fun workarounds like &lt;a href="https://www.joezimjs.com/javascript/the-lazy-mans-url-parsing/" rel="noopener noreferrer"&gt;The lazy man's URL parsing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When I wrote my first few JavaScript projects in 2016, I parsed and build URLs with all kinds giant loops and regex. The solutions were hard to read at best, and extremely buggy at worse.&lt;/p&gt;

&lt;p&gt;Then I used &lt;a href="https://www.w3schools.com/nodejs/met_path_join.asp" rel="noopener noreferrer"&gt;Node.js path.join() Method&lt;/a&gt; in my later projects. Until this week, I had assumed this was still the way to go. I even tried importing &lt;a href="https://github.com/browserify/path-browserify" rel="noopener noreferrer"&gt;browserify's path implementation for the browser&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  The modern JS way to URL handling
&lt;/h2&gt;

&lt;p&gt;If you've been sleeping under a rock like me, here's a quick overview of the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/URL" rel="noopener noreferrer"&gt;URL object&lt;/a&gt; from Web APIs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create a new URL object&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/path?param1=value1&amp;amp;param2=value2#section&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Parse the URL&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Host:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Host: example.com&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Path:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Path: /path&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Search Params:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// Search Params: param1=value1&amp;amp;param2=value2&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hash:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Hash: #section&lt;/span&gt;

&lt;span class="c1"&gt;// Update pars of the URL&lt;/span&gt;
&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new.example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;param3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#updated-section&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Recreate the URL&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rebuiltUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Update the URL object directly&lt;/span&gt;
&lt;span class="nx"&gt;rebuiltUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;param2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;updatedValue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Print the updated URL&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Rebuilt URL:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rebuiltUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Rebuilt URL: https://new.example.com/path?param1=value1&amp;amp;param2=updatedValue&amp;amp;param3=value3#updated-section&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Browser compatibility
&lt;/h2&gt;

&lt;p&gt;All remotely recent, modern browsers will support the URL library. Some methods may not be fully implemented, but the basic usage remains consistent. Find the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/URL#browser_compatibility" rel="noopener noreferrer"&gt;full browser compatibility table here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If &lt;strong&gt;you must support ancient browsers&lt;/strong&gt;, the &lt;a href="https://github.com/zloirock/core-js#url-and-urlsearchparams" rel="noopener noreferrer"&gt;core-js project&lt;/a&gt; provides a polyfill for older browsers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;Please use native Web APIs. Avoid building one off utility classes for common actions. JavaScript is constantly changing, I wrote my first lines of JS in 2016 and I'm constantly finding myself leaning into old, outdated information.&lt;/p&gt;

&lt;p&gt;If you have a moment, take a look through the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API" rel="noopener noreferrer"&gt;Web API docs&lt;/a&gt; from MDN. I guarantee you will find something new that solves a problem you needed to build your own solution for in the past.&lt;/p&gt;

&lt;h2&gt;
  
  
  A fun aside
&lt;/h2&gt;

&lt;p&gt;I still hate how the JavaScript &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date" rel="noopener noreferrer"&gt;Date object&lt;/a&gt;'s interface is so lacking.&lt;/p&gt;

&lt;p&gt;Try finding a date 5 days in the past, or comparing if two events happened 3 days apart.&lt;/p&gt;

&lt;p&gt;The fact that &lt;a href="https://momentjs.com/" rel="noopener noreferrer"&gt;moment.js&lt;/a&gt; or &lt;a href="https://day.js.org/" rel="noopener noreferrer"&gt;day.js&lt;/a&gt; needs to exist in 2024 bothers me a lot.&lt;/p&gt;

&lt;h2&gt;
  
  
  More fun stuff
&lt;/h2&gt;

&lt;p&gt;Come chat with me in these places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://x.com/WenYuGe1" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/wen-yu-ge/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gewenyu99" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>webapis</category>
      <category>beginners</category>
    </item>
    <item>
      <title>In technical writing, the devil is NOT in the details</title>
      <dc:creator>Vincent Ge</dc:creator>
      <pubDate>Mon, 13 May 2024 22:36:48 +0000</pubDate>
      <link>https://dev.to/gewenyu99/in-technical-writing-the-devil-is-not-in-the-details-36fn</link>
      <guid>https://dev.to/gewenyu99/in-technical-writing-the-devil-is-not-in-the-details-36fn</guid>
      <description>&lt;p&gt;I've been interviewing technical writers. What stands out about amazing candidates is that they spend surprisingly little time on the details. This surprises me and had me reflect that I look for, or rather, what makes a good technical writer.&lt;/p&gt;

&lt;p&gt;I think the assumption is that a good technical writer is made in the details. You want detailed, complete, and accurate documentation. This is what a lot of candidates focuses on, how they approach technical writing to make sure they understand the complete product use cases and accurately regurgitate that information on a page.&lt;/p&gt;

&lt;p&gt;If this is a candidate's focus, to me, that's a giant red flag 🚩&lt;/p&gt;

&lt;p&gt;As a technical writer, I don't think our value is in the ability to accurately recount the technical demos given to us by engineers. It's in structuring and filtering information to make it more accessible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExN2R2bGI4aGp0cWw5djZwdWlpMTE3c2FxNm1meW9yZjI2MnA5YXlqdCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Obh1Y5dpm4aGUoWaFw/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExN2R2bGI4aGp0cWw5djZwdWlpMTE3c2FxNm1meW9yZjI2MnA5YXlqdCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Obh1Y5dpm4aGUoWaFw/giphy.gif" alt="Man flipping through book" width="400" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Take a look at the R manual, for example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr7v1gmcjcp1263sgy06m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr7v1gmcjcp1263sgy06m.png" alt="R manual screenshot" width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Complete? *&lt;em&gt;Yes! *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Detailed? &lt;strong&gt;Yes!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Accurate? &lt;strong&gt;Definitely!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Would I, or anyone learning R read this thing? &lt;strong&gt;Well, this part is debatable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's a long running joke among technical writers that if your documentation requires a "how to read" section, you've done it wrong.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff32num6a8honry59a52x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff32num6a8honry59a52x.png" alt="GNU docs, how to read docs section" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Technical writing serves a clear purpose, making information accessible. Getting accurate information is the easy part, any engineer can do a better job than you, because you didn't build the feature.&lt;/p&gt;

&lt;p&gt;As a technical writer, &lt;strong&gt;your job is to abstract, structure, and enhance information to make it easier to access&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once more for emphasis:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Your job is to abstract, structure, and enhance information to make it easier to access&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The devil is not in the details. The information is not novel. What's novel is the presentation and structure that lets a developer find what they need in 5 minutes, not 50.&lt;/p&gt;

&lt;h2&gt;
  
  
  Here's what I look for in a technical writer
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Start with the journey
&lt;/h3&gt;

&lt;p&gt;Good docs flow from most general information to least, commonly needed workflows to power moves, and low resolution to high resolution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fesi07wds4qy7jn6i93tt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fesi07wds4qy7jn6i93tt.png" alt="Vue documentation" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give me &lt;strong&gt;only the information I need&lt;/strong&gt; at a given stage in my learning process. Don't overwhelm me. Keep the examples simple, even if I'm missing details.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fif2yycj1ybp9e2n4wrsy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fif2yycj1ybp9e2n4wrsy.png" alt="Bun docs" width="800" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick wins
&lt;/h3&gt;

&lt;p&gt;Your docs serve 0 purpose unless someone reads them. To encourage them, like a benevolent teacher, you need to give them confidence that you're leading them somewhere.&lt;/p&gt;

&lt;p&gt;Quick wins encourage the readers to keep exploring. Attention span and patience are finite, you gotta fight for it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbwqrbtw74s8msunbsrk4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbwqrbtw74s8msunbsrk4.png" alt="React docs" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Information hierarchy
&lt;/h3&gt;

&lt;p&gt;Most important information first. I need to scan the start of a page to know if the page contains the information I'm looking for. I need to scan the start of a paragraph to know if the details are relevant.&lt;/p&gt;

&lt;p&gt;Take this paragraph for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Security is important in development. That's why you should take care to protect secrets. Secrets should be safely stored as a environment variable in a vault. You can find vaults under **settings** &amp;gt; **security** &amp;gt; **vault**. Don't share this with anyone!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the same information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Store secrets as environment variables in vaults by navigating to **settings** &amp;gt; **security** &amp;gt; **vault**. Your secrets should never be shared. You must ensure data privacy, sharing secrets can compromise security during development.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same information, one is easier to read than others.&lt;/p&gt;

&lt;h3&gt;
  
  
  Know the reader
&lt;/h3&gt;

&lt;p&gt;Some products expect expert readers. Some products expect novice readers.&lt;/p&gt;

&lt;p&gt;Take a look at the &lt;a href="https://planetscale.com/docs/concepts/what-is-planetscale"&gt;getting started guide for PlanetScale&lt;/a&gt; and &lt;a href="https://supabase.com/docs/guides/getting-started/quickstarts/reactjs"&gt;getting started guide for Supabase&lt;/a&gt;,&lt;/p&gt;

&lt;p&gt;One focuses on high-level information to help senior devs make decisions on the tech stack, one focuses on quick wins and short examples to help you get your foot in the door.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessible links (bonus)
&lt;/h3&gt;

&lt;p&gt;One thing I've started to look for is accessible links. How many times have you seen an embedded link like &lt;a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"&gt;this&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Your links should be complete and have full context for it to be more accessible. While some can tell what a link does from context, those using accessibility tools might find it annoying to figure out where a link leads.&lt;/p&gt;

&lt;h4&gt;
  
  
  Do this ✅:
&lt;/h4&gt;

&lt;p&gt;You can &lt;a href="https://dev.toLINK"&gt;learn more about database indexing in our documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Not this ❌:
&lt;/h4&gt;

&lt;p&gt;You can learn more about database indexing &lt;a href="https://dev.toLINK"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bottom line
&lt;/h3&gt;

&lt;p&gt;I care about accuracy in docs. If this is all I had to care about, I'd just make everyone read API references.&lt;/p&gt;

&lt;p&gt;Fact of the matter is, I need to help a developer go from 0 to 1, but also experts to scale from 1K to 1M. You do this by abstracting and structuring information, so focus on your ability to do this on your next interview.&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>writing</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>How I use Appwrite Databases with Pinia to build my own habit tracker</title>
      <dc:creator>Vincent Ge</dc:creator>
      <pubDate>Mon, 15 Apr 2024 02:25:44 +0000</pubDate>
      <link>https://dev.to/gewenyu99/how-i-use-appwrite-databases-with-pinia-to-build-my-own-habit-tracker-3a20</link>
      <guid>https://dev.to/gewenyu99/how-i-use-appwrite-databases-with-pinia-to-build-my-own-habit-tracker-3a20</guid>
      <description>&lt;h2&gt;
  
  
  Some context
&lt;/h2&gt;

&lt;p&gt;I'm building a project with Vue and Appwrite called Sisyphus. Much like Sisyphus, we have many tasks that are a real uphill grind everyday. And like Sisyphus, we repeatedly push that figurative boulder up the hill each day, anyway.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://github.com/gewenyu99/sisyphus/tree/main"&gt;Sisyphus&lt;/a&gt; to help track my own habits with an UI like a GitHub commit history.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9z82x6qlgjwxm5452kdp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9z82x6qlgjwxm5452kdp.png" alt="Image description" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a GitHub commit history for reference.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4q7mir8l9znz91lio2p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4q7mir8l9znz91lio2p.png" alt="Image description" width="800" height="557"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My inspiration
&lt;/h2&gt;

&lt;p&gt;I used Appwrite to implement authentication and database APIs for my app. The SDK API is very similar to a REST API, which gives you lots of flexibility. &lt;/p&gt;

&lt;p&gt;Inspired by a video by my colleague &lt;a class="mentioned-user" href="https://dev.to/dennisivy11"&gt;@dennisivy11&lt;/a&gt; I decided to share how I use Appwrite Databases, too.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://www.youtube.com/watch?si=mVM-SRlBhhiooHMd&amp;amp;v=1ip2aljprvg&amp;amp;feature=youtu.be" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://res.cloudinary.com/practicaldev/image/fetch/s--7RYao8QF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.ytimg.com/vi/1ip2aljprvg/maxresdefault.jpg" height="450" class="m-0" width="800"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://www.youtube.com/watch?si=mVM-SRlBhhiooHMd&amp;amp;v=1ip2aljprvg&amp;amp;feature=youtu.be" rel="noopener noreferrer" class="c-link"&gt;
          Do this when writing database queries with Appwrite - YouTube
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          How to write less, and cleaner code when working with an Appwrite database.Instructor: https://twitter.com/dennisivy11 / https://www.linkedin.com/in/dennis-i...
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://res.cloudinary.com/practicaldev/image/fetch/s--oYjCIs0Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.youtube.com/s/desktop/49be3691/img/favicon.ico" width="16" height="16"&gt;
        youtube.com
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Here's how you can use Appwrite Databases with Vue 3, Pinia, and TypeScript to create elegant data stores that can be consumed in your components.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Databases API
&lt;/h2&gt;

&lt;p&gt;Here's what CRUD operations look like with Appwrite's SDKs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// create a document&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; 
&lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sisyphys&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;boulders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// read some documents&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listDocuments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sisyphys&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;boulders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// update a document&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sisyphys&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;boulders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drink-water&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;food&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;baz&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// delete a document&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deleteDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sisyphys&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;boulders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drink-water&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple, RESTful, but not useful for building a UI. I need these operations to play nice with a store like &lt;a href="https://pinia.vuejs.org/"&gt;Pinia&lt;/a&gt; to &lt;strong&gt;manage state&lt;/strong&gt; and &lt;strong&gt;share data across components&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  My data
&lt;/h2&gt;

&lt;p&gt;I also have two collections in Appwrite with the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Pushes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Document&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;boulderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Boulder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Document&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pushes&lt;/strong&gt; tracks each time I pushed a boulder forward, it describes &lt;strong&gt;how far I pushed&lt;/strong&gt;, &lt;strong&gt;on which day&lt;/strong&gt;, and which &lt;strong&gt;goal/boulder&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boulder&lt;/strong&gt; tracks the goals I've created, their &lt;strong&gt;name&lt;/strong&gt;, a short &lt;strong&gt;description&lt;/strong&gt;, and the goal of how far I'd ideally push that boulder forward in a day in &lt;strong&gt;distance&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Looking back at the UI, the data needs to be consumed at these levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My boulder container component, which creates a boulder card for each goal I set.&lt;/li&gt;
&lt;li&gt;Each boulder card, which needs to render a history of my progress on that goal.&lt;/li&gt;
&lt;li&gt;Each form in my app, like when I create new goals or push a boulder and move my goal forward.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating a generic store for Appwrite collections
&lt;/h2&gt;

&lt;p&gt;All Appwrite collections share these commonalities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Belong to a database and has it's own ID&lt;/li&gt;
&lt;li&gt;Data extends &lt;code&gt;Models.Document&lt;/code&gt;, containing information like &lt;code&gt;$id&lt;/code&gt;, &lt;code&gt;permissions&lt;/code&gt;, &lt;code&gt;$createdAt&lt;/code&gt;, &lt;code&gt;$updatedAt&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;Has &lt;code&gt;create&lt;/code&gt;, &lt;code&gt;list&lt;/code&gt;, &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means code related to these commonalities are going to be the same for each store I need for each Appwrite Databases Collection.&lt;/p&gt;

&lt;p&gt;So, here's how I implement a base collection for all my Appwrite Collections.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generic interface
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This is my base store&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Models&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/lib/appwrite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pinia&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;defineCollection&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;defineStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;getters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="c1"&gt;// we'll cover these later&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="c1"&gt;// we'll cover these later&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My collection store is a factory that returns a define store based on the name of the collection, the database id, and the collection id.&lt;/p&gt;

&lt;p&gt;Notice the use of the generic &lt;code&gt;Type&lt;/code&gt;, we pass this in from each collection store that composes/extends this base Pinia store with the structure of the collection.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This is how I extend my store with a type&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Boulder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Document&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;COLLECTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;boulders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collectionStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;defineCollection&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Boulder&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;collection-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;COLLECTION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_DATABASES_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;COLLECTION&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here I'm extending the base collection store with the type &lt;code&gt;&amp;lt;Boulder&amp;gt;&lt;/code&gt; so I get helpful type hints when I consume the store.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;state&lt;/strong&gt; of the store is a list of documents that extends this type.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getters and actions
&lt;/h3&gt;

&lt;p&gt;Pinia implements getters and actions to help you interact with the app. Here's the generic getters and actions used by all my collection stores.&lt;/p&gt;

&lt;p&gt;Here are my getters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ... skipped code in my base collection store&lt;/span&gt;
    &lt;span class="nx"&gt;getters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I don't need a lot of fancy getters in my base store, just something to return a count.&lt;/p&gt;

&lt;p&gt;Here are my CRUD actions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ... skipped code in my base collection store&lt;/span&gt;
    &lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;id&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listDocuments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;queries&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
          &lt;span class="nx"&gt;data&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;data&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// ... more actions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They just implement a simpler version of the existing SDK methods. Let's me reduce my method calls from this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sisyphys&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;boulders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why use many lines when few lines do trick?&lt;/p&gt;

&lt;p&gt;Some more actions I implement  are &lt;code&gt;.load()&lt;/code&gt; and &lt;code&gt;.all()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// ... previous actions&lt;/span&gt;
      &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;batchSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;batchSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;batchSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batchSize&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
          &lt;span class="nx"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batchSize&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;after&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cursorAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
            &lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;I call &lt;code&gt;collection.load()&lt;/code&gt; to initialize or update the state of my store and &lt;code&gt;collection.all()&lt;/code&gt; lets me responsibly paginate through all my data. These are utility methods I need in all my collection stores.&lt;/p&gt;

&lt;p&gt;Of course, if you need methods like &lt;code&gt;collection.page()&lt;/code&gt; or &lt;code&gt;collection.nextPage()&lt;/code&gt; for a paginated store, you can extend the base store with more actions.&lt;/p&gt;

&lt;p&gt;Sisyphus is quite simple, so we load all the data at once.&lt;/p&gt;

&lt;p&gt;All these methods make the store generic enough that I will use all these methods in every collection I interface with and provide a more elegant interface to consume data as a store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extending my generic store
&lt;/h2&gt;

&lt;p&gt;Remember how I mentioned I extend all my stores? Here's my boulders store as an example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// My boulders store&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Boulder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Document&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;COLLECTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;boulders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collectionStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;defineCollection&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Boulder&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;collection-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;COLLECTION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_DATABASES_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;COLLECTION&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useBoulders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;COLLECTION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;collectionStore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;boulders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;boulder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;boulder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;boulder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;boulder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Boulder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;boulder&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;boulders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;boulder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I define the data structure expected to be returned by the store and call &lt;code&gt;defineCollection&amp;lt;Boulder&amp;gt;&lt;/code&gt; to create this store.&lt;/p&gt;

&lt;p&gt;Then, I use Pinia's composition API to extend it with a few methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I instantiate the generic collectionStore as the parent store.&lt;/li&gt;
&lt;li&gt;I then add a &lt;code&gt;getter&lt;/code&gt; called &lt;code&gt;boulders&lt;/code&gt; that just returns a computed with the value of &lt;code&gt;parent.documents&lt;/code&gt;. This ensures I &lt;strong&gt;maintain reactivity&lt;/strong&gt;, which means when &lt;code&gt;parent.documents&lt;/code&gt; changes, my Vue 3 components are notified.&lt;/li&gt;
&lt;li&gt;Here's some ✨ magic ✨ , &lt;code&gt;boulder()&lt;/code&gt; is a getter that takes in a boulder ID, searches for it, and returns it. Because it's a computed value, it is also &lt;strong&gt;reactive&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;I then wrapped the parents &lt;code&gt;load&lt;/code&gt; and &lt;code&gt;create&lt;/code&gt; methods to use less generic names that make more sense when correlated with the verbs I use in UI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's another example with my &lt;code&gt;Pushes&lt;/code&gt; collection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Models&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/lib/appwrite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pinia&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineCollection&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./collection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;computed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getToday&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/lib/date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Boulder&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./boulders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Pushes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Document&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;boulderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;COLLECTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pushes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collectionStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;defineCollection&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Pushes&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;collection-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;COLLECTION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_DATABASES_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;COLLECTION&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usePushes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;COLLECTION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;collectionStore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pushes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;boulderId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;boulderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;([],&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;boulderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getToday&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;boulderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boulderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Pushes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pushes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;push&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While I load all pushes at once, each boulder card needs to get filtered results from the store. I again use &lt;code&gt;computed&lt;/code&gt; to make sure the filtered value is reactive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consuming these stores
&lt;/h2&gt;

&lt;p&gt;Important note about consuming stores like this is that you will need to convert them to &lt;code&gt;refs&lt;/code&gt; so they retain reactivity in your UI.&lt;/p&gt;

&lt;p&gt;For this, we can destructure them like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="nx"&gt;setup&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// ... skipped imports&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useBoulders&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/stores/boulders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;boulder&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;storeToRefs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;useBoulders&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Boulder&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boulder in boulders&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;boulderId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boulder.$id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would render a list of boulders and dynamically render new ones when the boulders document changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other fun things to think about
&lt;/h2&gt;

&lt;p&gt;When needed, you can extend these stores with &lt;a href="https://appwrite.io/docs/apis/realtime"&gt;Realtime&lt;/a&gt;, so that they can listen to updates updates in a collection made by other users in realtime. This would be great for chat apps, etc.&lt;/p&gt;

&lt;p&gt;If you're interested in seeing realtime or pagination added to one of these stores, let me know in the comments.&lt;/p&gt;

&lt;p&gt;If you haven't tried &lt;a href="https://appwrite.io/"&gt;Appwrite&lt;/a&gt;, make sure you give it a spin. It's a open source backend that packs authentication, databases, storage, serverless functions, and all kinds of utilities in a neat API. Appwrite can be self-hosted, or you can use Appwrite Cloud starting with a generous free plan.&lt;/p&gt;

&lt;p&gt;Cheers~&lt;/p&gt;

</description>
      <category>vue</category>
      <category>pinia</category>
      <category>appwrite</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Hackathons: A Comedy of Errors, Sleep Deprivation, and Unforgettable Memories</title>
      <dc:creator>Vincent Ge</dc:creator>
      <pubDate>Wed, 24 May 2023 14:22:11 +0000</pubDate>
      <link>https://dev.to/appwrite/hackathons-a-comedy-of-errors-sleep-deprivation-and-unforgettable-memories-1eai</link>
      <guid>https://dev.to/appwrite/hackathons-a-comedy-of-errors-sleep-deprivation-and-unforgettable-memories-1eai</guid>
      <description>&lt;p&gt;Have you tried explaining a Hackathon to someone who’s not a developer or a student?&lt;/p&gt;

&lt;p&gt;No really, thinking back to my first Hackathon, Hack the North 2017 in the University of Waterloo, I’m not sure how I’d explain it to family and friends.&lt;/p&gt;

&lt;p&gt;I was with 1000+ sweaty strangers with their laptops, stuck in the same building on campus, coding straight for 36+ hours without sleep, sustained solely by Soylent and caffeine chocolates. Then we present the abomination of a project we dreamed up to be judged by a panel of professionals to be our first impressions. The best part, we did it &lt;strong&gt;voluntarily&lt;/strong&gt; and thought it was &lt;strong&gt;fun&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffumx2vixjpagpqhfo9o4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffumx2vixjpagpqhfo9o4.jpg" alt="Hackers at Junction 2015" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;
Hackers at Junction 2015



&lt;p&gt;Hysterical and borderline insane. This is probably how someone outside the development community would see us, if we didn’t provide anymore context.&lt;/p&gt;

&lt;p&gt;So what are hackathons really like and why do thousands of developers still flock to them, every year?&lt;/p&gt;

&lt;p&gt;Well, here’s the typical experience of every first-time hacker.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤩 The pre-Hackathon frenzy
&lt;/h2&gt;

&lt;p&gt;It all starts with that fateful moment when you stumble upon a hackathon announcement online. Your eyes light up, your heart races, and you begin fantasizing about the possibilities. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;“What if I win a prize? What if my skills are recognized by a sponsor and I receive a job offer? What if I finally get the perfect idea for my startup, and this is my chance to impress investors,”&lt;/em&gt; you thought, scrolling through the list of companies sponsoring the hackathon.&lt;/p&gt;

&lt;h2&gt;
  
  
  🕵️‍♂️ The great team hunt
&lt;/h2&gt;

&lt;p&gt;Hackathons are a bit like a speed dating show for techies. You're searching for the perfect match to complement your skills and bring your ideas to life. So, you mingle, make small talk, and subtly show off your coding prowess. &lt;/p&gt;

&lt;p&gt;Finally, you assemble a team that's a blend of mad scientists, digital wizards, and that one person who takes a nap 12 hours into the hackathon, never to be seen again until 5 minutes before the submission deadline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7g8qnkivdux9l7k9hoey.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7g8qnkivdux9l7k9hoey.jpg" alt="Many team hunts start on Slack or Discord, where attendees mingle before the event begins." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
Many team hunts start on Slack or Discord, where attendees mingle before the event begins.



&lt;h2&gt;
  
  
  💡 Abundant optimism, romanticism, and naivety
&lt;/h2&gt;

&lt;p&gt;Now comes the brainstorming session. A whirlwind of caffeine and adrenaline fueled excitement and an avalanche of ideas. It's a creative explosion of clever solutions to solve the world's most pressing problems. &lt;/p&gt;

&lt;p&gt;With each idea, your team becomes more confident and more excited. You begin to actually believe in your ideas and your team’s skill to pull it off. You start wondering why no one has thought of these ideas before, and if they’ll make you the next Elon Musk. &lt;/p&gt;

&lt;h2&gt;
  
  
  🌙 The Sleepless Sprint
&lt;/h2&gt;

&lt;p&gt;With your collective genius focused on a single goal, you embark on a coding marathon that feels like a high-stakes heist movie. As the clock ticks down, your team deftly navigates a labyrinth of code, design, and countless energy drinks. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ysshuvdpd0r45kj1ijm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ysshuvdpd0r45kj1ijm.png" alt="Caffeine chocolates like this are frequently handed out to hackers during MLH hackathons." width="800" height="588"&gt;&lt;/a&gt;&lt;/p&gt;
Caffeine chocolates like this are frequently handed out to hackers during MLH hackathons.



&lt;p&gt;While the star programmer of the team pumps out mountains of code, you and friend help your other teammate to setup Git, debug NPM, and complain about his choice to bring a Windows laptop to a hackathon.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sevens stages of compromise
&lt;/h2&gt;

&lt;p&gt;You look up, 5 hours left on the clock, reality starts to sink in, and your grand vision crumbles like a cookie under the weight of deadlines. And you begin wondering if your missing teammate, on a hunt for swag and snacks, has fallen asleep on a toilet.&lt;/p&gt;

&lt;p&gt;In a matter of minutes, you blitz through the stages of grief. Shock, denial, anger, bargaining, depressing, finally accepting your fate, you begin to discuss alternate plans with your team.&lt;/p&gt;

&lt;p&gt;You cut features, make sacrifices, fake data, and pick out your favorite powerpoint template. In the end, wrap your Frankenstein's monster of a project in an elegant looking frontend interface, and hide it all behind a confident, charismatic presentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎤 The Moment of Truth
&lt;/h2&gt;

&lt;p&gt;With frayed nerves and weary smiles, you stand before the judges to present your project. The room is filled with the collective groans of sleep-deprived brains and the faint smell of coffee. &lt;/p&gt;

&lt;p&gt;As you share your team's story and make up answers to questions you have no response, you can't help but feel a sense of pride in your sleep-deprived, caffeine-driven accomplishment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj4dqvuyz9zjd0f8ebkmw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj4dqvuyz9zjd0f8ebkmw.png" alt="TC Hackathon at Disrupt Berlin" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;
Winning team at TC Hackathon at Disrupt Berlin.



&lt;p&gt;That sense of pride quickly vanishes as “that one team” team walks in with a fully functioning electro-magnetic scanner that detects cancer and solves global warming at the same time. Hardware and algorithm, developed in under 36 hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  👋 The Bittersweet Goodbye
&lt;/h2&gt;

&lt;p&gt;As the hackathon comes to a close, you part ways with your newfound friends, bonded by shared memories of chaos, laughter, and caffeine-induced hallucinations. You may not have changed the world, but you've emerged as a more resilient, adaptable, and slightly twitchy version of yourself.&lt;/p&gt;

&lt;p&gt;You also start considering an alternate career path. There’s no way you’re competing with people from “that one team,” who completed a project you couldn’t finish in 36 months, and somehow had better hair, too.&lt;/p&gt;

&lt;p&gt;Nevertheless, you made it, and it’s a moment you’ll never forget.&lt;/p&gt;

&lt;h2&gt;
  
  
  💫 The Appwrite Hashnode Hackathon
&lt;/h2&gt;

&lt;p&gt;In person hackathons are stressful. But the Appwrite Hashnode hackathon isn’t. You can sign up today to participate from the comfort of your home, over the internet. &lt;/p&gt;

&lt;p&gt;No sweaty gymnasiums, fighting over ethernet ports, sleepless nights, and wondering if the room smells like farts or the pile of takeout someone spilled in the corner.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hashnode.com/hackathons/appwrite"&gt;Join today with your team&lt;/a&gt;, for a chance to win up to $5000 USD and limited edition Appwrite swag!&lt;/p&gt;

</description>
      <category>hackathon</category>
      <category>beginners</category>
      <category>ideas</category>
      <category>career</category>
    </item>
    <item>
      <title>Introducing DateTime Support and ISO 8601 Dates</title>
      <dc:creator>Vincent Ge</dc:creator>
      <pubDate>Wed, 14 Sep 2022 13:14:03 +0000</pubDate>
      <link>https://dev.to/appwrite/introducing-datetime-support-and-iso-8601-dates-25di</link>
      <guid>https://dev.to/appwrite/introducing-datetime-support-and-iso-8601-dates-25di</guid>
      <description>&lt;p&gt;In Appwrite 1.0, we’re introducing a lot of new features. Among them is one of our most frequently requested features, DateTime support. Appwrite Databases now provides a dedicated DateTime attribute for storing date and time information. All Appwrite endpoints will also now return timestamps that are in DateTime format following &lt;a href="https://en.wikipedia.org/wiki/ISO_8601"&gt;ISO 8601&lt;/a&gt; specification.&lt;/p&gt;

&lt;p&gt;First time hearing about &lt;a href="https://appwrite.io/"&gt;Appwrite&lt;/a&gt;? Appwrite is an open-source backend-as-a-service that abstracts all the complexity involved in building a modern application by providing you with a set of REST APIs for your core backend needs. Appwrite handles user authentication and authorization, real-time databases, cloud functions, webhooks, and much more!&lt;/p&gt;

&lt;h2&gt;
  
  
  Common DateTime Representations
&lt;/h2&gt;

&lt;p&gt;There are two main representations of storing date and time in computing, &lt;a href="https://en.wikipedia.org/wiki/Unix_time"&gt;Unix Time&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/ISO_8601"&gt;DateTime&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Unix Time, also known as Epoch time or Posix time, stores time since Unix Epoch on January 1, 1970 at midnight UTC. Unix Time is expressed and stored as a 32 bit integer. &lt;/p&gt;

&lt;p&gt;DateTime stores time as a string. The format of the string is human readable, expressing year, month, date, hour, seconds, and milliseconds. For example, &lt;code&gt;2022-08-17T00:00:00&lt;/code&gt;, which is the &lt;strong&gt;Midnight of August 17th, 2022 in UTC&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;The advantage of using Unix Time is that it’s stored as an integer. This makes Unix Time easier to store, query, and sort. A 32 bit integer takes up less space than a DateTime string and is less computationally intensive to do comparisons and arithmetic with. Unix Time is also independent of timezones, making it perfect for representing time on the web. &lt;/p&gt;

&lt;p&gt;The downside to Unix Time is that it’s not easily human readable and limited in date range. Unix Time cannot store time before 1970 or after 2038, which is very limiting.&lt;/p&gt;

&lt;p&gt;DateTime following ISO 8601 is highly readable by a human and the default storage format for JSON. The potential downside of DateTime is that it can be timezone ambiguous and somewhat expensive to store and sort.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why DateTime?
&lt;/h2&gt;

&lt;p&gt;Supporting DateTime attribute is one of the most frequently requested features we receive. The upsides of being highly readable by humans and being more flexible is highly valued to many Appwrite developers. &lt;/p&gt;

&lt;p&gt;We also decided to use DateTime for Appwrite’s response objects because it’s the default format provided by JavaScript’s &lt;code&gt;Date()&lt;/code&gt; object. This helps us stay consistent.&lt;/p&gt;

&lt;p&gt;To remove timezone ambiguity, Appwrite will handle and store all DateTime in UTC timezone. Unix Timestamps are still supported by Appwrite's Databases Service through 32 bit integers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples
&lt;/h2&gt;

&lt;p&gt;Most languages will support both DateTime following ISO 8601 and Unix Timestamps in their system library. I’ll take JavaScript as an example to show you how to manipulate DateTime strings.&lt;/p&gt;

&lt;p&gt;To create a datetime string of the current time in UTC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Gets current date and time in UTC&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// Print current date and time in UTC&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just as easily, you can parse a UTC DateTime string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A DateTime string in UTC, converted to date object&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2022-08-24T20:41:21.909Z&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the dateTime string will have a &lt;code&gt;Z&lt;/code&gt; at the end to denote UTC. When Javascript parses this string, it will automatically convert it to your machine’s timezone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;One of the biggest benefits of building open source software in full publicity is the real-time feedback we receive from the community. It allows us to hear more voices and iterate faster. &lt;/p&gt;

&lt;p&gt;This new feature was the result of many suggestions and discussions on GitHub. I wanted to shout out those who pitched this idea to us time and time again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/appwrite/appwrite/discussions/2944"&gt;Discussion #2944&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/appwrite/appwrite/discussions/2909"&gt;Discussion #2909&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/appwrite/appwrite/discussions/2451#discussioncomment-1760684"&gt;Discussion #2451&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/appwrite/appwrite/issues/3018"&gt;Issue #3018&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your feedback is as valuable as code contributions and we encourage readers to pitch your thoughts to us. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📝 &lt;a href="https://github.com/appwrite/appwrite/discussions"&gt;Appwrite GitHub Discussions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🚀 &lt;a href="https://github.com/appwrite"&gt;Appwrite Github&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📜 &lt;a href="https://appwrite.io/docs"&gt;Appwrite Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;a href="https://appwrite.io/discord"&gt;Discord Community&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
      <category>appwrite</category>
    </item>
    <item>
      <title>Appwrite Loves Open Source: Why I Choose To Support Kdenlive</title>
      <dc:creator>Vincent Ge</dc:creator>
      <pubDate>Wed, 03 Aug 2022 20:58:10 +0000</pubDate>
      <link>https://dev.to/appwrite/appwrite-loves-open-source-why-i-choose-to-support-kdenlive-37em</link>
      <guid>https://dev.to/appwrite/appwrite-loves-open-source-why-i-choose-to-support-kdenlive-37em</guid>
      <description>&lt;p&gt;To me, the open-source community is about coming together and building cool stuff that empowers others. This shouldn't be limited to building developer oriented tools like TensorFlow and Jest. Open-source can also empower those outside the developer community by removing barriers to accessing software tools.&lt;/p&gt;

&lt;p&gt;Open-source is at the heart ❤️ of everything we do at Appwrite, and we want to enable and foster the open-source community that helped us grow to over 16K stars ⭐ on GitHub. At Appwrite, we recognize how difficult it can be to build open-source software. Sustaining a project and a team takes enormous commitment, and getting adequate funding is difficult. This is especially true to open-source projects that don't face developers, making it harder to attract contributors. That's why Appwrite allows each engineer to pick an open-source project to sponsor. &lt;/p&gt;

&lt;p&gt;When Appwrite offered to sponsor an open-source project on my behalf, I jumped at this opportunity to support &lt;a href="https://kdenlive.org/en/" rel="noopener noreferrer"&gt;Kdenlive&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Equal Representation in Media
&lt;/h2&gt;

&lt;p&gt;Everyone deserves a voice. Creating videos to raise awareness to problems, tell stories, or share cool ideas is an important avenue to be heard in today's world. The problem is that while videos are effective, they are also expensive to make. Even with increasingly cheap access to relatively high quality cameras and microphones, video editing software keeps getting more expensive. &lt;/p&gt;

&lt;p&gt;With Adobe Premiere Pro priced $52.99 USD/month and Final Cut Pro priced at $299.99 USD, their high price shuts out many individuals and communities from having a voice. This can lead to many important stories and ideas to be underrepresented, even in the age of the internet. Young creators, or those with less funds to create videos (like myself when I was younger), can't express their ideas. Entire segments of the population might be shutout of YouTube, TikTok, and other media platforms as a result. These segments of the population are often most in need of a voice.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Kdenlive?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fddonyoykdnv8u4tg5l9w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fddonyoykdnv8u4tg5l9w.png" alt="Image description" width="800" height="441"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://kdenlive.org/en/" rel="noopener noreferrer"&gt;Kdenlive&lt;/a&gt; is a fully featured video editor and provides a multitrack workflow similar to other mainstream software like Adobe Premiere. Kdenlive comes packaged with enough video/audio effects, transitions, fonts, configurable hot-keys, and supported input/output formats that allows a user to develop a workflow just as effective and efficient as Adobe Premiere. To rival the industry is surprisingly difficult to achieve with creative software, especially for an open-source project, because of the amount proprietary knowledge required to replicate the vast set of features. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why I choose Kdenlive
&lt;/h2&gt;

&lt;p&gt;What I love about Kdenlive is their regularity and transparency in communication. They are fully transparent about their road maps, efforts, community, and how they spend donation money. Each year, Kdenlive creates a &lt;a href="https://ev.kde.org/reports/ev-2020/" rel="noopener noreferrer"&gt;report&lt;/a&gt; detailing everything that they do in a year, and their plans moving forward.&lt;/p&gt;

&lt;p&gt;Here are some upcoming features I'm excited about. The Kdenlive team is working to implement nested timelines like in Premiere Pro. This allows you to work on small timelines for different parts of a production, then nest them within a larger timeline to apply affects to them together, reducing repetitive work. Another feature is “embedded effect”, which allows you to apply effects to &lt;strong&gt;all&lt;/strong&gt; clips at once, instead of one by one (like in Premiere :P). They're also working to increase responsiveness and UX of the software.&lt;/p&gt;

&lt;p&gt;Like Appwrite, Kdenlive aims to provide an open-source alternative to existing expensive, close-sourced software. Kdenlive is also not a carbon copy of Premiere Pro, they try to fill a similar niche, but have their own opinions and philosophy behind development. What's not to love?&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn More About Appwrite
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://appwrite.io/" rel="noopener noreferrer"&gt;Appwrite&lt;/a&gt; is an open-source Backend-as-a-Service (BaaS), packaged as a set of Docker micro-services, to give developers of any background the tools necessary to build modern apps quickly and securely. &lt;/p&gt;

&lt;p&gt;Check out Appwrite as the backend for your next web, Flutter, or server-side application. Here are some handy links for more information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📚&lt;a href="https://github.com/appwrite/appwrite/blob/master/CONTRIBUTING.md" rel="noopener noreferrer"&gt;Appwrite Contribution Guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬&lt;a href="https://appwrite.io/discord" rel="noopener noreferrer"&gt;Appwrite Discord&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🚀&lt;a href="https://github.com/appwrite" rel="noopener noreferrer"&gt;Appwrite Github&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📜&lt;a href="https://appwrite.io/docs" rel="noopener noreferrer"&gt;Appwrite Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>appwrite</category>
      <category>videoediting</category>
      <category>kdenlive</category>
    </item>
    <item>
      <title>How Do I Index My Appwrite Collection?</title>
      <dc:creator>Vincent Ge</dc:creator>
      <pubDate>Tue, 28 Jun 2022 18:49:11 +0000</pubDate>
      <link>https://dev.to/appwrite/how-do-i-index-my-appwrite-collection-2o4n</link>
      <guid>https://dev.to/appwrite/how-do-i-index-my-appwrite-collection-2o4n</guid>
      <description>&lt;p&gt;The Appwrite Hackathon on DEV has just concluded and one of the frequent pain points among new Appwrite developers is with adding indexes to collection. When attempting to query a collection or sort the documents returned, Appwrite developers frequently faced errors related to missing or incorrect indexes. This post will cover what indexes are, why we need them, and how to use them correctly in Appwrite.&lt;/p&gt;

&lt;h1&gt;
  
  
  What’s an Index?
&lt;/h1&gt;

&lt;p&gt;An index helps you search a collection of documents more efficiently. Appwrite requires an index for all sort and query type operations to ensure your application runs efficiently.&lt;/p&gt;

&lt;p&gt;Imagine a collection as a catalog of different movies, where each movie has a unique and random ID and the catalog is sorted by ID. If you know the movie’s ID, finding the movie is easy. If you wanted to look up a film by a particular title, you might have to look through the entire catalog to find the one movie. That’s a lot of work! If the catalog had an index of sorted movie titles and their corresponding ID, on the other hand, you’ll have a much easier time finding movies.&lt;/p&gt;

&lt;p&gt;An index in a database works in a very similar way, only that they’re implemented using data structures such as B-Trees. The performance of an index lookup is &lt;code&gt;O(log(n))&lt;/code&gt;, while a full table scan is of &lt;code&gt;O(n)&lt;/code&gt;. That's a huge difference!&lt;/p&gt;

&lt;p&gt;Indexes found in Appwrite rely on the implementation found in the underlying database. As of today, Appwrite only supports MariaDB as a database adaptor, so the behavior of indexes and querying indexes should be very similar to that of MariaDB.&lt;/p&gt;

&lt;h2&gt;
  
  
  When do I need an index?
&lt;/h2&gt;

&lt;p&gt;For performance reasons, Appwrite requires an index for every query and sort operation. If you need to query multiple attributes in the same query, you’ll need an index with multiple attributes. Appwrite supports three types of indexes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Key: Plain indexes that allow you to perform sorts and queries.&lt;/li&gt;
&lt;li&gt;Unique: Indexes that don’t allow duplicates, useful for indexing unique identifiers.&lt;/li&gt;
&lt;li&gt;Fulltext: Indexes that allow you to query string attributes that may not match the query parameter exactly. You can use this field type to look for keywords in movie descriptions, for example.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When an index is created, an equivalent index is created in the underlying database (MariaDB) to make efficient queries and sorting possible.&lt;/p&gt;

&lt;h1&gt;
  
  
  Using Indexes in Appwrite
&lt;/h1&gt;

&lt;p&gt;Before you can consume an index, you’ll need to first create them. I’ll be using a collection of Disney movies as example to show you how to create different types of indexes and how to query them. The collection configuration and code referenced can be found in &lt;a href="https://gist.github.com/gewenyu99/091f0e9e34d77586657a052a9c835f53"&gt;this Gist&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The collection belongs to a database called &lt;code&gt;entertainment&lt;/code&gt; has the following attributes:&lt;/p&gt;

&lt;p&gt;We will create indexes to allow querying for movies by title, querying unique movies by title and year, as well as searching movie descriptions using keywords.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Indexes
&lt;/h2&gt;

&lt;p&gt;To search for movies by title, we need to create an index with the name &lt;code&gt;title&lt;/code&gt; and type &lt;code&gt;Key&lt;/code&gt;. This allow us to query for all movies that share the same title. The match is exact.&lt;/p&gt;

&lt;p&gt;To find unique movies and filter out remakes or duplicate titles, we can create a &lt;code&gt;Unique&lt;/code&gt; index named &lt;code&gt;title-and-year&lt;/code&gt;. Notice how this index includes both &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;year&lt;/code&gt; attributes. This is necessary to query both attributes at the same time.&lt;/p&gt;

&lt;p&gt;To search for keywords in a movie’s description, we can create a &lt;code&gt;Fulltext&lt;/code&gt; index named &lt;code&gt;description&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Querying Using Indexes
&lt;/h2&gt;

&lt;p&gt;To find every version of “The Lion King”, the query will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Point sdk to entertainment database&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Databases&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;entertainment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Query for the movie "The Lion King"&lt;/span&gt;
&lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listDocuments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;movies&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The Lion King&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)]),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will returned the following:&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;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"documents"&lt;/span&gt;&lt;span class="p"&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="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"The Lion King"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"The animated remake of Lion King"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"$id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"62a793e62d7c105ee92f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"$read"&lt;/span&gt;&lt;span class="p"&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="nl"&gt;"$write"&lt;/span&gt;&lt;span class="p"&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="nl"&gt;"$collection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"movies"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1994&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"The Lion King"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"The original cartoon Lion King from Disney."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"$id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"62a793e631cce5281683"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"$read"&lt;/span&gt;&lt;span class="p"&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="nl"&gt;"$write"&lt;/span&gt;&lt;span class="p"&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="nl"&gt;"$collection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"movies"&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="p"&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;p&gt;To find only the 1950 edition of Cinderella, the query will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listDocuments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;movies&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cinderella&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;year&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1950&lt;/span&gt;&lt;span class="p"&gt;)]),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will returned the following:&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="s2"&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;"total"&lt;/span&gt;&lt;span class="p"&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;"documents"&lt;/span&gt;&lt;span class="p"&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="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1950&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Cinderella"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"With a wicked stepmother (Wilfred Jackson) and two jealous stepsisters (Homer Brightman, Harry Reeves) who keep her enslaved and in rags, Cinderella (Clyde Geronimi) stands no chance of attending the royal ball."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"$id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"62a793e6886c41162589"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"$read"&lt;/span&gt;&lt;span class="p"&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="nl"&gt;"$write"&lt;/span&gt;&lt;span class="p"&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="nl"&gt;"$collection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"movies"&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="p"&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;p&gt;Finally, to search for all remakes, we can search descriptions for the keyword &lt;code&gt;remake&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listDocuments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;movies&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;remake&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This yields the following results:&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;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"documents"&lt;/span&gt;&lt;span class="p"&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="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"The Lion King"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"The animated remake of Lion King"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"$id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"62a793e62d7c105ee92f"&lt;/span&gt;&lt;span class="nl"&gt;"$read"&lt;/span&gt;&lt;span class="p"&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="nl"&gt;"$write"&lt;/span&gt;&lt;span class="p"&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="nl"&gt;"$collection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"movies"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2015&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Cinderella"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"The 2015 remake of the original animated Cinderella."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"$id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"62a793e688f4ff05f620"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"$read"&lt;/span&gt;&lt;span class="p"&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="nl"&gt;"$write"&lt;/span&gt;&lt;span class="p"&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="nl"&gt;"$collection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"movies"&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="p"&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;p&gt;Et voilà! That’s how you create and query indexes with Appwrite! &lt;/p&gt;

&lt;h1&gt;
  
  
  Final Remarks
&lt;/h1&gt;

&lt;p&gt;Indexes come at a cost. They require additional data structures which costs processing to create and maintain on writes, as well as more storage space. It’s a trade off to get faster reads for slightly slower writes speeds and more storage space. As a developer, you should be conscious of which attributes you need to query and only create necessary indexes.&lt;/p&gt;

&lt;h1&gt;
  
  
  More resources
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;🚀 &lt;a href="https://appwrite.io/docs/databases"&gt;Databases Guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🚀 &lt;a href="https://github.com/appwrite"&gt;Appwrite Github&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📜 &lt;a href="https://appwrite.io/docs"&gt;Appwrite Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;a href="https://appwrite.io/discord"&gt;Discord Community&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>appwrite</category>
      <category>database</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
