<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Will Ceolin</title>
    <description>The latest articles on Forem by Will Ceolin (@ceolinwill).</description>
    <link>https://forem.com/ceolinwill</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%2F174745%2F9eb36645-f88a-443d-b57c-91d9ea454eba.jpg</url>
      <title>Forem: Will Ceolin</title>
      <link>https://forem.com/ceolinwill</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ceolinwill"/>
    <language>en</language>
    <item>
      <title>Where's the layer panel/variable mode in Figma UI3</title>
      <dc:creator>Will Ceolin</dc:creator>
      <pubDate>Sun, 17 Nov 2024 16:32:37 +0000</pubDate>
      <link>https://forem.com/ceolinwill/wheres-the-layer-panelvariable-mode-in-figma-ui3-1ho0</link>
      <guid>https://forem.com/ceolinwill/wheres-the-layer-panelvariable-mode-in-figma-ui3-1ho0</guid>
      <description>&lt;p&gt;Most tutorials showing how to create light/dark mode toggles in Figma tell you to go to the &lt;em&gt;Layer&lt;/em&gt; section in the &lt;em&gt;Design&lt;/em&gt; panel. However, that panel was renamed in the latest UI3 Figma design.&lt;/p&gt;

&lt;p&gt;You can go to &lt;em&gt;Appearance -&amp;gt; Apply variable mode&lt;/em&gt; (it's the first icon) to accomplish the same thing now:&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%2F0x81zsqrzkb34w9fbsz4.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%2F0x81zsqrzkb34w9fbsz4.png" alt="Screenshot of the Figma design panel showing the apply variable mode option" width="468" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>figma</category>
      <category>design</category>
      <category>ui</category>
    </item>
    <item>
      <title>How to run a local Phoenix app on another machine</title>
      <dc:creator>Will Ceolin</dc:creator>
      <pubDate>Wed, 08 May 2024 16:09:55 +0000</pubDate>
      <link>https://forem.com/ceolinwill/how-to-run-a-local-phoenix-app-on-another-machine-1hmd</link>
      <guid>https://forem.com/ceolinwill/how-to-run-a-local-phoenix-app-on-another-machine-1hmd</guid>
      <description>&lt;p&gt;When you create a new Phoenix app, it uses the &lt;code&gt;127.0.0.1&lt;/code&gt; IP address by default. To run it locally on another machine (e.g. your phone), you need to change this on &lt;code&gt;config/dev.exs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Find the &lt;code&gt;http&lt;/code&gt; config on that file. You’ll see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="ss"&gt;http:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;ip:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;127&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="mi"&gt;0&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="ss"&gt;port:&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, change it to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="ss"&gt;http:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;ip:&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="mi"&gt;0&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;port:&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it! Now you’ll be able to test your Phoenix app locally on other machines by using your network’s IP address.&lt;/p&gt;

</description>
      <category>phoenix</category>
      <category>elixir</category>
    </item>
    <item>
      <title>Phoenix LiveView: When to use navigate, patch, href, redirect, push_patch, push_navigate</title>
      <dc:creator>Will Ceolin</dc:creator>
      <pubDate>Wed, 18 Oct 2023 11:47:59 +0000</pubDate>
      <link>https://forem.com/ceolinwill/phoenix-liveview-when-to-use-navigate-patch-href-redirect-pushpatch-pushnavigate-6pl</link>
      <guid>https://forem.com/ceolinwill/phoenix-liveview-when-to-use-navigate-patch-href-redirect-pushpatch-pushnavigate-6pl</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;navigate&lt;/code&gt; and &lt;code&gt;push_navigate&lt;/code&gt;: When you're navigating to the same &lt;code&gt;live_session&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;patch&lt;/code&gt; and &lt;code&gt;push_patch&lt;/code&gt;: When you're navigating to the same module/page/liveview. For example, when only the attributes change - from &lt;code&gt;/posts/1&lt;/code&gt; to &lt;code&gt;/posts/2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;href&lt;/code&gt; and &lt;code&gt;redirect&lt;/code&gt;: When you're navigating to another live session or outside a live view.&lt;/li&gt;
&lt;li&gt;Don't use &lt;code&gt;navigate&lt;/code&gt; when navigating to another &lt;code&gt;live_session&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Let's say we have the following routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;live_session&lt;/span&gt; &lt;span class="ss"&gt;:public_routes&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;HomeLive&lt;/span&gt;
  &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/posts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;PostsLive&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;live_session&lt;/span&gt; &lt;span class="ss"&gt;:private_routes&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/posts/:id/content, PostEdit, :content
  live "&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;PostEdit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:metadata&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have two live sessions: &lt;code&gt;public_routes&lt;/code&gt; and &lt;code&gt;private_routes&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  From / to /posts
&lt;/h3&gt;

&lt;p&gt;In this case, we'll use &lt;code&gt;navigate&lt;/code&gt; (client) or &lt;code&gt;push_navigate&lt;/code&gt; (server) because we're navigating to the same &lt;code&gt;public_routes&lt;/code&gt; live session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;.&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="n"&gt;navigate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sx"&gt;~p"/posts"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;Posts&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/.&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will keep the browser page but mount a new LiveView process to reload its content only.&lt;/p&gt;

&lt;h3&gt;
  
  
  From /posts to /posts/:id/content
&lt;/h3&gt;

&lt;p&gt;In this case, we'll use &lt;code&gt;href&lt;/code&gt; (client) or &lt;code&gt;redirect&lt;/code&gt; (server) because we're moving from the &lt;code&gt;public_routes&lt;/code&gt; live session to the &lt;code&gt;private_routes&lt;/code&gt; one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;.&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sx"&gt;~p"/posts/#{post.id}/content"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;Edit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/.&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will cause a full page reload.&lt;/p&gt;

&lt;h3&gt;
  
  
  From /posts/:id/content to /posts/:id/metadata
&lt;/h3&gt;

&lt;p&gt;In this case, we'll use &lt;code&gt;patch&lt;/code&gt; (client) or &lt;code&gt;push_patch&lt;/code&gt; (server) because we're navigating to the same page/module. We're only changing the page attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;.&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sx"&gt;~p"/posts/:id/metadata"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;Metadata&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/.&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When using &lt;code&gt;patch&lt;/code&gt; you'll also need to define a &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#c:handle_params/3" rel="noopener noreferrer"&gt;&lt;code&gt;handle_params&lt;/code&gt; callback&lt;/a&gt; to handle the parameters change.&lt;/p&gt;

&lt;p&gt;While &lt;code&gt;navigate&lt;/code&gt; mounts a new live view, &lt;code&gt;patch&lt;/code&gt; updates the current live view, sending less data back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;I recently &lt;a href="https://github.com/phoenixframework/phoenix_live_view/issues/2861" rel="noopener noreferrer"&gt;made a mistake&lt;/a&gt; where the topbar would be displayed twice while navigating between different live sessions. &lt;a href="https://github.com/phoenixframework/phoenix_live_view/issues/2861#issuecomment-1767049732" rel="noopener noreferrer"&gt;This happens because I tried to&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;live navigate (&amp;lt;.link navigate) across live sessions, which requires an extra round trip to the server and looks like the cause of your unwanted behavior. Regular link href is all you need here and it will not dispatch the loading event in that case, resolving the behavior you're seeing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reading the &lt;a href="https://hexdocs.pm/phoenix_live_view/welcome.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt;, it was confusing to me to understand the difference between all those options, especially because I was looking at the wrong places.&lt;/p&gt;

&lt;p&gt;I was looking the &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#push_navigate/2" rel="noopener noreferrer"&gt;push_navigate/2&lt;/a&gt; and &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#push_patch/2" rel="noopener noreferrer"&gt;push_patch/2&lt;/a&gt; docs but a better explanation is available on the &lt;a href="https://hexdocs.pm/phoenix_live_view/live-navigation.html" rel="noopener noreferrer"&gt;live navigation&lt;/a&gt; and &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#link/1-attributes" rel="noopener noreferrer"&gt;link attributes&lt;/a&gt; docs.&lt;/p&gt;

</description>
      <category>phoenix</category>
      <category>elixir</category>
      <category>liveview</category>
    </item>
    <item>
      <title>How to get the host from a LiveView Socket</title>
      <dc:creator>Will Ceolin</dc:creator>
      <pubDate>Mon, 25 Sep 2023 17:09:35 +0000</pubDate>
      <link>https://forem.com/ceolinwill/how-to-get-the-host-from-a-liveview-socket-23nn</link>
      <guid>https://forem.com/ceolinwill/how-to-get-the-host-from-a-liveview-socket-23nn</guid>
      <description>&lt;p&gt;First, you need to allow the WebSocket to display the URI. You can do it by editing your &lt;code&gt;endpoint.ex&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="s2"&gt;"/live"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;LiveView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;websocket:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;connect_info:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;session:&lt;/span&gt; &lt;span class="nv"&gt;@session_options&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example above, we added &lt;code&gt;:uri&lt;/code&gt; the &lt;code&gt;connect_info&lt;/code&gt; websocket option.&lt;/p&gt;

&lt;p&gt;Now, we can create a mount hook to get access to the &lt;code&gt;host&lt;/code&gt; value. In the example below, we'll add the host to our socket assigns, so we can use it in our LiveView with &lt;code&gt;socket.assigns.host&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# host_plug.ex&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;on_mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:add_host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;host:&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;LiveView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_connect_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Component&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;host:&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:cont&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you need to add this mount hook to our &lt;code&gt;live_session&lt;/code&gt; in our &lt;code&gt;router.ex&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;MyAppWeb&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;pipe_through&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:browser&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="n"&gt;live_session&lt;/span&gt; &lt;span class="ss"&gt;:my_routes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;on_mount:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="no"&gt;MyAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;HostHook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:add_host&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;HomeLive&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Now we can get the host in our LiveView by using &lt;code&gt;socket.assigns.host&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;Note: If you're getting an error like this: &lt;code&gt;** (MatchError) no match of right hand side value: nil&lt;/code&gt;, it might mean that you forgot to add &lt;code&gt;:uri&lt;/code&gt; to your &lt;code&gt;connect_info&lt;/code&gt; as I mentioned on the first step.&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>phoenix</category>
    </item>
    <item>
      <title>How to use stripe-mock with GitHub Actions</title>
      <dc:creator>Will Ceolin</dc:creator>
      <pubDate>Mon, 26 Jun 2023 17:36:08 +0000</pubDate>
      <link>https://forem.com/ceolinwill/how-to-use-stripe-mock-with-github-actions-325</link>
      <guid>https://forem.com/ceolinwill/how-to-use-stripe-mock-with-github-actions-325</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/stripe/stripe-mock" rel="noopener noreferrer"&gt;stripe-mock&lt;/a&gt; is a library that allows you to mock the Stripe API. It's useful for testing purposes when you don't want to make requests to the actual API.&lt;/p&gt;

&lt;p&gt;You can also use it with GitHub Actions on the testing pipeline for your CI environment.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;stripe-mock&lt;/code&gt; has a Docker image you can use on GitHub Actions: &lt;code&gt;stripe/stripe-mock&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This allows us to use &lt;a href="https://docs.github.com/en/actions/using-containerized-services/about-service-containers" rel="noopener noreferrer"&gt;service containers&lt;/a&gt; to run the mock server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;stripemock&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stripe/stripe-mock:latest&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;12111:12111'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Now the mock Stripe API will be running on port &lt;code&gt;12111&lt;/code&gt; when you run your tests on GitHub Actions.&lt;/p&gt;

</description>
      <category>stripe</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>How to fix the "Callback info about the Mix.Task behaviour is not available." error from Dialyzer</title>
      <dc:creator>Will Ceolin</dc:creator>
      <pubDate>Wed, 12 Apr 2023 19:44:23 +0000</pubDate>
      <link>https://forem.com/ceolinwill/how-to-fix-the-callback-info-about-the-mixtask-behaviour-is-not-available-error-from-dialyzer-482h</link>
      <guid>https://forem.com/ceolinwill/how-to-fix-the-callback-info-about-the-mixtask-behaviour-is-not-available-error-from-dialyzer-482h</guid>
      <description>&lt;p&gt;Add this to the &lt;code&gt;dialyzer&lt;/code&gt; options in your &lt;code&gt;mix.exs&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="ss"&gt;dialyzer:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="ss"&gt;plt_add_apps:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:mix&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;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>elixir</category>
      <category>dialyzer</category>
      <category>mix</category>
    </item>
    <item>
      <title>Unit testing a page title on Phoenix LiveView</title>
      <dc:creator>Will Ceolin</dc:creator>
      <pubDate>Fri, 07 Apr 2023 20:49:45 +0000</pubDate>
      <link>https://forem.com/ceolinwill/unit-testing-a-page-title-on-phoenix-liveview-b3j</link>
      <guid>https://forem.com/ceolinwill/unit-testing-a-page-title-on-phoenix-liveview-b3j</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;page_title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="s2"&gt;"my page title"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Phoenix LiveView has a &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#live_title/1" rel="noopener noreferrer"&gt;live_title/1&lt;/a&gt; component that allows us to easily update a page title and see those changes reflect in the browser.&lt;/p&gt;

&lt;p&gt;They also have a &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#page_title/1" rel="noopener noreferrer"&gt;page_title/1&lt;/a&gt; function to test if your page title was updated using the &lt;code&gt;page_title&lt;/code&gt; assign.&lt;/p&gt;

&lt;p&gt;You can use it to make sure you're assigning a &lt;code&gt;page_title&lt;/code&gt; to your pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:page_title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"My page title"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>elixir</category>
      <category>phoenix</category>
      <category>liveview</category>
      <category>testing</category>
    </item>
    <item>
      <title>How to clear Elixir warnings on VSCode</title>
      <dc:creator>Will Ceolin</dc:creator>
      <pubDate>Fri, 07 Apr 2023 20:36:52 +0000</pubDate>
      <link>https://forem.com/ceolinwill/how-to-clear-elixir-warnings-on-vscode-j3g</link>
      <guid>https://forem.com/ceolinwill/how-to-clear-elixir-warnings-on-vscode-j3g</guid>
      <description>&lt;p&gt;After making changes to my Phoenix project, VSCode often gives me &lt;a href="https://elixirforum.com/t/lsp-warnings-on-latest-release-of-liveview/54726" rel="noopener noreferrer"&gt;warnings&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Phoenix.LiveView.TagEngine.component/3 defined in application :phoenix_live_view is used by the current application but the current application does not depend on :phoenix_live_view.&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Invalid type specification for function&lt;/code&gt; coming from the &lt;code&gt;ElixirLS Dialyzer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;And other warnings that I don't remember now.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are some the things that help me to clear those warnings on VSCode:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete the &lt;code&gt;_build&lt;/code&gt;, &lt;code&gt;.elixir_ls&lt;/code&gt; and &lt;code&gt;deps&lt;/code&gt; directories.&lt;/li&gt;
&lt;li&gt;Reinstall dependencies: &lt;code&gt;mix deps.get&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;With an &lt;code&gt;.ex&lt;/code&gt; file open, go to the Command Palette (Cmd + Shift + P) and run these commands: &lt;code&gt;Elixir: Trigger mix clean --deps in language server&lt;/code&gt; and &lt;code&gt;Elixir: Restart language server&lt;/code&gt;, then restart VSCode.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If Elixir (not VSCode) starts complaining about routes not found, then you can delete the &lt;code&gt;_build&lt;/code&gt; directory and compile your project again (&lt;code&gt;mix compile&lt;/code&gt;).&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>phoenix</category>
      <category>vscode</category>
    </item>
    <item>
      <title>How to find what changed between two Phoenix versions</title>
      <dc:creator>Will Ceolin</dc:creator>
      <pubDate>Tue, 07 Mar 2023 21:46:36 +0000</pubDate>
      <link>https://forem.com/ceolinwill/how-to-find-what-changed-between-two-phoenix-versions-12hk</link>
      <guid>https://forem.com/ceolinwill/how-to-find-what-changed-between-two-phoenix-versions-12hk</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Just use &lt;a href="https://www.phoenixdiff.org/" rel="noopener noreferrer"&gt;Phoenix Diff&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://hexdocs.pm/phoenix/mix_tasks.html" rel="noopener noreferrer"&gt;Phoenix generators&lt;/a&gt; are great and flexible because they don't have any magic into it. You can easily change the generated code. However, this flexibility comes with a price: When something changes, it can be hard to find out what you need to change in your generated code as well.&lt;/p&gt;

&lt;p&gt;As someone new to Elixir &amp;amp; Phoenix, that's an issue I'm often facing - and so is &lt;a href="https://elixirforum.com/u/coladarci" rel="noopener noreferrer"&gt;Greg Coladarci&lt;/a&gt;, who &lt;a href="https://elixirforum.com/t/the-future-of-upgrading-phoenix-liveview-apps-with-generated-code/54404" rel="noopener noreferrer"&gt;posted about this on the Elixir Forum&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That's how I found out about &lt;a href="https://www.phoenixdiff.org/" rel="noopener noreferrer"&gt;Phoenix Diff&lt;/a&gt;, a tool created by &lt;a href="https://github.com/navinpeiris" rel="noopener noreferrer"&gt;Navin Peiris&lt;/a&gt; and maintained by &lt;a href="https://github.com/aaronrenner" rel="noopener noreferrer"&gt;Aaron Renner&lt;/a&gt;. Thank you, &lt;a href="https://elixirforum.com/u/al2o3cr/summary" rel="noopener noreferrer"&gt;Matt Jones&lt;/a&gt;, for the &lt;a href="https://elixirforum.com/t/the-future-of-upgrading-phoenix-liveview-apps-with-generated-code/54404/3" rel="noopener noreferrer"&gt;tip&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>phoenix</category>
    </item>
    <item>
      <title>36 companies hiring globally</title>
      <dc:creator>Will Ceolin</dc:creator>
      <pubDate>Fri, 17 Feb 2023 08:16:11 +0000</pubDate>
      <link>https://forem.com/ceolinwill/36-companies-hiring-globally-42d2</link>
      <guid>https://forem.com/ceolinwill/36-companies-hiring-globally-42d2</guid>
      <description>&lt;p&gt;I often wasted time applying to "remote" jobs just to find out they were "US only", "East Coast only", "Europe only", etc.&lt;/p&gt;

&lt;p&gt;It was kinda hard to find companies that hire globally. So, I've created a list to compile them all. I'm adding the initial list below but you can keep contribute to it on GitHub:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ceolinwill" rel="noopener noreferrer"&gt;
        ceolinwill
      &lt;/a&gt; / &lt;a href="https://github.com/ceolinwill/global-hiring" rel="noopener noreferrer"&gt;
        global-hiring
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A list of companies hiring globally.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Global Hiring&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Have you ever spent a lot of time applying for a remote job just to find out the company you're applying to doesn't hire in your country
This happened to me. That's why I've decided to create a list of companies hiring globally.&lt;/p&gt;
&lt;p&gt;My goal here is to group companies with a global hiring policy. This means: Companies that don't have restrictions on where you can live
Companies hiring US only, East Coast only, Europe only, etc. shouldn't be included here.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Some context:&lt;/strong&gt; There are lots of lists for remote companies. &lt;a href="https://github.com/yanirs" rel="noopener noreferrer"&gt;@yanirs&lt;/a&gt; has this &lt;a href="https://github.com/yanirs/established-remote" rel="noopener noreferrer"&gt;awesome established remote companies list&lt;/a&gt;, for example. We also have websites such as &lt;a href="https://remoteok.com/" rel="nofollow noopener noreferrer"&gt;RemoteOK&lt;/a&gt; and &lt;a href="https://www.findasync.com/" rel="nofollow noopener noreferrer"&gt;FindAsync&lt;/a&gt; where we can find remote jobs. However, many remote companies don't hire globally.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Contributions are welcome.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why hiring globally?&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Some of the benefits include (feel free to improve this list):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Access to a larger talent pool:&lt;/strong&gt; When…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ceolinwill/global-hiring" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  The list
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Business/products&lt;/th&gt;
&lt;th&gt;Languages&lt;/th&gt;
&lt;th&gt;Open Salary&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://37signals.com/" rel="noopener noreferrer"&gt;37 Signals&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Productivity tools&lt;/td&gt;
&lt;td&gt;Ruby&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.acceleratorapp.co/" rel="noopener noreferrer"&gt;AcceleratorApp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Incubators, accelerators&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.airbase.com/" rel="noopener noreferrer"&gt;Airbase&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;FinTech&lt;/td&gt;
&lt;td&gt;Python, JavaScript&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://almanac.io/" rel="noopener noreferrer"&gt;Almanac&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Collaboration tools&lt;/td&gt;
&lt;td&gt;Ruby, JavaScript&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://automattic.com/" rel="noopener noreferrer"&gt;Automattic&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Open-source tools&lt;/td&gt;
&lt;td&gt;PHP, JavaScript&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://buffer.com/" rel="noopener noreferrer"&gt;Buffer&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Social media tools&lt;/td&gt;
&lt;td&gt;PHP, JavaScript&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cal.com" rel="noopener noreferrer"&gt;Cal&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Scheduling infrastructure&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://deepgram.com/" rel="noopener noreferrer"&gt;Deepgram&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Speech recognition&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://deno.com/" rel="noopener noreferrer"&gt;Deno&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Developer Tools / PaaS&lt;/td&gt;
&lt;td&gt;Rust, TypeScript, WASM&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://designstripe.com/" rel="noopener noreferrer"&gt;designstripe&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Design tools&lt;/td&gt;
&lt;td&gt;TypeScript, Python&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doist.com/" rel="noopener noreferrer"&gt;Doist&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Productivity&lt;/td&gt;
&lt;td&gt;Python, TypeScript&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://empower.me/" rel="noopener noreferrer"&gt;Empower&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;FinTech&lt;/td&gt;
&lt;td&gt;C#, Kotlin, Swift&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://fly.io/" rel="noopener noreferrer"&gt;Fly.io&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Cloud hosting&lt;/td&gt;
&lt;td&gt;Rust, Go, Ruby, Elixir&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.forem.com/" rel="noopener noreferrer"&gt;Forem&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Blogging platform&lt;/td&gt;
&lt;td&gt;Ruby, JavaScript&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://ghost.org/" rel="noopener noreferrer"&gt;Ghost&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Blogging platform&lt;/td&gt;
&lt;td&gt;JavaScript&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.giantswarm.io/" rel="noopener noreferrer"&gt;Giant Swarm&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://gumroad.com/" rel="noopener noreferrer"&gt;Gumroad&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Platform for creators&lt;/td&gt;
&lt;td&gt;Ruby, JavaScript&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://invisible.co" rel="noopener noreferrer"&gt;Invisible&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Operations as a Service&lt;/td&gt;
&lt;td&gt;Python, Typescript&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://iterative.ai/" rel="noopener noreferrer"&gt;Iterative&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Machine learning&lt;/td&gt;
&lt;td&gt;Python, TypeScript&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.migaku.io/" rel="noopener noreferrer"&gt;Migaku&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Language learning&lt;/td&gt;
&lt;td&gt;JavaScript&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://opencraft.com/" rel="noopener noreferrer"&gt;OpenCraft&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;E-learning&lt;/td&gt;
&lt;td&gt;Python, JavaScript&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://ox.work/" rel="noopener noreferrer"&gt;Ox&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Coversation coach&lt;/td&gt;
&lt;td&gt;Python, TypeScript&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://pitch.com/" rel="noopener noreferrer"&gt;Pitch&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Presentation tools&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://protocol.ai/" rel="noopener noreferrer"&gt;Protocol Labs&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Web3&lt;/td&gt;
&lt;td&gt;JavaScript, Python, Go, Rust&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://proxify.io/" rel="noopener noreferrer"&gt;Proxify&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Hiring/vetting developers&lt;/td&gt;
&lt;td&gt;Java, Golang&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://remote.com/" rel="noopener noreferrer"&gt;Remote&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Global hiring and payroll&lt;/td&gt;
&lt;td&gt;Elixir, TypeScript&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://rtcamp.com/" rel="noopener noreferrer"&gt;rtCamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;WordPress projects&lt;/td&gt;
&lt;td&gt;PHP, JavaScript&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://safetywing.com/" rel="noopener noreferrer"&gt;SafetyWing&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Insurance for nomads&lt;/td&gt;
&lt;td&gt;Java, Kotlin, JavaScript&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.servicebell.com/" rel="noopener noreferrer"&gt;Service Bell&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Live chat&lt;/td&gt;
&lt;td&gt;Python, TypeScript&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.spruceid.com/" rel="noopener noreferrer"&gt;Spruce&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Identity control&lt;/td&gt;
&lt;td&gt;JavaScript&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://standardbots.com/" rel="noopener noreferrer"&gt;Standard Bots&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Robotics&lt;/td&gt;
&lt;td&gt;C++, TypeScript&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.stickermule.com/" rel="noopener noreferrer"&gt;Sticker Mule&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;E-commerce&lt;/td&gt;
&lt;td&gt;Golang, JavaScript&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://unsplash.com/" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Stock photos&lt;/td&gt;
&lt;td&gt;Ruby&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.xapo.com/" rel="noopener noreferrer"&gt;Xapo Bank&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Banking, crypto&lt;/td&gt;
&lt;td&gt;Python, Kotlin&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://wikimediafoundation.org/" rel="noopener noreferrer"&gt;Wikimedia&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Non-profit&lt;/td&gt;
&lt;td&gt;PHP, Python, Go&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://zapier.com/" rel="noopener noreferrer"&gt;Zapier&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Automation tools&lt;/td&gt;
&lt;td&gt;Python, JavaScript&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>hiring</category>
      <category>career</category>
    </item>
    <item>
      <title>How to add a link to an image in Phoenix</title>
      <dc:creator>Will Ceolin</dc:creator>
      <pubDate>Sat, 11 Jun 2022 20:49:00 +0000</pubDate>
      <link>https://forem.com/ceolinwill/how-to-add-a-link-to-an-image-in-phoenix-10g2</link>
      <guid>https://forem.com/ceolinwill/how-to-add-a-link-to-an-image-in-phoenix-10g2</guid>
      <description>&lt;p&gt;&lt;strong&gt;Edit (2023-05-11):&lt;/strong&gt; This is much simpler in Phoenix 1.7+ thanks to &lt;a href="https://hexdocs.pm/phoenix/Phoenix.VerifiedRoutes.html" rel="noopener noreferrer"&gt;Verified Routes&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="na"&gt;link&lt;/span&gt; &lt;span class="na"&gt;navigate=&lt;/span&gt;&lt;span class="s"&gt;{~p"/post/123"}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;{~p"/images/logo.png"}&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Logo"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nt"&gt;link&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Let's say you have an image and you want to link it to another route in the Phoenix Framework. You can use &lt;a href="https://hexdocs.pm/phoenix_html/Phoenix.HTML.Link.html#link/2-examples" rel="noopener noreferrer"&gt;link/2&lt;/a&gt; with a code block to accomplish that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;link&lt;/span&gt; &lt;span class="na"&gt;to:&lt;/span&gt; &lt;span class="na"&gt;Routes.page_path&lt;/span&gt;&lt;span class="err"&gt;(@&lt;/span&gt;&lt;span class="na"&gt;conn&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;:index&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="na"&gt;do&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;{Routes.static_path(@conn,&lt;/span&gt; &lt;span class="err"&gt;"/&lt;/span&gt;&lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;phoenix.png&lt;/span&gt;&lt;span class="err"&gt;")}&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Phoenix Framework Logo"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example above, we'll link the Phoenix logo (&lt;code&gt;phoenix.png&lt;/code&gt;) to the home page.&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>phoenix</category>
    </item>
    <item>
      <title>13 features I wish Firebase had</title>
      <dc:creator>Will Ceolin</dc:creator>
      <pubDate>Tue, 25 Jan 2022 14:32:24 +0000</pubDate>
      <link>https://forem.com/ceolinwill/13-features-i-wish-firebase-had-1i5d</link>
      <guid>https://forem.com/ceolinwill/13-features-i-wish-firebase-had-1i5d</guid>
      <description>&lt;p&gt;I love &lt;a href="https://firebase.google.com/" rel="noopener noreferrer"&gt;Firebase&lt;/a&gt;. I love how quickly it allows me to develop a new product without having to worry about infrastructure. I feel like Firebase is a dream for indie developers.&lt;/p&gt;

&lt;p&gt;However, Firebase isn't perfect. There are some important features I wish they would work on. Some of those are now handled by products such as &lt;a href="https://planetscale.com/" rel="noopener noreferrer"&gt;PlanetScale&lt;/a&gt; and &lt;a href="https://www.xata.io/" rel="noopener noreferrer"&gt;Xata&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But I love using Firebase. I think they have the potential to really be a BaaS (Backend-as-a-Service), providing everything you need to run a backend without having to worry about infrastructure and scale.&lt;/p&gt;

&lt;p&gt;Yesterday, &lt;a href="https://twitter.com/mbleigh" rel="noopener noreferrer"&gt;Michael Bleigh&lt;/a&gt; from Firebase, asked this on Twitter:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1485665341439033345-538" src="https://platform.twitter.com/embed/Tweet.html?id=1485665341439033345"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1485665341439033345-538');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1485665341439033345&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;So, I've decided to write this post in response with the most important features I wish Firebase would have right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Full-text Search
&lt;/h2&gt;

&lt;p&gt;I don't think I ever worked on a project where I didn't need full-text search at some point. We usually end up having to use something like &lt;a href="https://www.elastic.co/app-search/" rel="noopener noreferrer"&gt;Elastic&lt;/a&gt; or &lt;a href="https://www.algolia.com/" rel="noopener noreferrer"&gt;Algolia&lt;/a&gt;. They're great services and &lt;a href="https://firebase.google.com/docs/firestore/solutions/search" rel="noopener noreferrer"&gt;Firebase even recommend them&lt;/a&gt; but it would be great if full-text search would be included in their SDK, at least for Firestore.&lt;/p&gt;

&lt;p&gt;I hope one day we can simply run a query 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postsRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&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;query&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;searchCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postsRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;da vinci&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;That query would return all posts where the word &lt;code&gt;da vinci&lt;/code&gt; is mentioned based on a ranking that we could define using a configuration file (&lt;code&gt;e.g. firestore.search.json&lt;/code&gt;):&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="nl"&gt;"posts"&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;"priority"&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="err"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'author'&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;We could also override the priority when we run our query by specifying which fields we want to search on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postsRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&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;query&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;searchCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postsRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;da vinci&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;author&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;The query above would run a full-text search on the &lt;code&gt;author&lt;/code&gt; property only.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Aggregation queries
&lt;/h2&gt;

&lt;p&gt;When I need data aggregation I either use Cloud Functions (for simpler aggregations) or I don't use Firebase at all. I know data aggregation is hard to accomplish on Firestore but it would be a game-changer for their product if they manage to do it.&lt;/p&gt;

&lt;p&gt;I'd love to see queries like these:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postsRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&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;queryRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postsRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;author&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;==&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;davinci&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;postsCount&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;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryRef&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 242&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where I count the number of documents in a given collection based on a specific query. The query above, for example, would return the number of posts a specific user has published.&lt;/p&gt;

&lt;p&gt;I'd love to see similar queries for other data aggregations such as &lt;code&gt;sum&lt;/code&gt;, &lt;code&gt;average&lt;/code&gt;, &lt;code&gt;min&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt;, etc. For example, a few years ago I was working on this IoT application where we needed to display real-time aggregated data for energy consumption. We weren't using Firebase for that but, given Firebase's real-time capabilities, it would have been amazing to use it if it would support data aggregation 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;consumptionRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;consumption&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;queryRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;consumptionRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;customerID&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;==&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;uid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;createdAt&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;&amp;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="s1"&gt;2021-01-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;createdAt&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;&amp;lt;=&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;2021-01-31&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hour&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;consumption&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;sum&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;kwh&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;The query above would sum all values from the &lt;code&gt;kwh&lt;/code&gt; field grouped by hour based on the &lt;code&gt;createdAt&lt;/code&gt; property. It would return something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2021-01-01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"kwh"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.05&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;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2021-01-01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"kwh"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.04&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;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2021-01-01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"kwh"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.03&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="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In theory, I'm already able to accomplish something similar to that using Cloud Functions where I'd have an &lt;code&gt;onWrite&lt;/code&gt; trigger for the &lt;code&gt;consumption&lt;/code&gt; collection and it would update a &lt;code&gt;_dataAggregation/{collectionName}/{consumerID}/{dateGroup}&lt;/code&gt; document (i.e. &lt;code&gt;_dataAggregation/consumption/123/2021_01_01_01_00_00&lt;/code&gt;). But that's too much work, so I don't use Firebase when I need that kind of aggregation.&lt;/p&gt;

&lt;p&gt;However, I'd love if Firestore would allow us to setup data aggregations similar to how we're able to have indexes. We could have a &lt;code&gt;firestore.aggregation.json&lt;/code&gt; file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"collectionName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"consumption"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"operation: "&lt;/span&gt;&lt;span class="err"&gt;sum&lt;/span&gt;&lt;span class="s2"&gt;",
  "&lt;/span&gt;&lt;span class="err"&gt;kind&lt;/span&gt;&lt;span class="s2"&gt;": { "&lt;/span&gt;&lt;span class="err"&gt;type&lt;/span&gt;&lt;span class="s2"&gt;": "&lt;/span&gt;&lt;span class="err"&gt;date&lt;/span&gt;&lt;span class="s2"&gt;", "&lt;/span&gt;&lt;span class="err"&gt;field&lt;/span&gt;&lt;span class="s2"&gt;": "&lt;/span&gt;&lt;span class="err"&gt;createdAt&lt;/span&gt;&lt;span class="s2"&gt;" },
  "&lt;/span&gt;&lt;span class="err"&gt;groupBy&lt;/span&gt;&lt;span class="s2"&gt;": ["&lt;/span&gt;&lt;span class="err"&gt;hour&lt;/span&gt;&lt;span class="s2"&gt;", "&lt;/span&gt;&lt;span class="err"&gt;day&lt;/span&gt;&lt;span class="s2"&gt;", "&lt;/span&gt;&lt;span class="err"&gt;month&lt;/span&gt;&lt;span class="s2"&gt;", "&lt;/span&gt;&lt;span class="err"&gt;year&lt;/span&gt;&lt;span class="s2"&gt;"],
  "&lt;/span&gt;&lt;span class="err"&gt;filters&lt;/span&gt;&lt;span class="s2"&gt;": ["&lt;/span&gt;&lt;span class="err"&gt;customerID&lt;/span&gt;&lt;span class="s2"&gt;", "&lt;/span&gt;&lt;span class="err"&gt;roomID&lt;/span&gt;&lt;span class="s2"&gt;"],
  "&lt;/span&gt;&lt;span class="err"&gt;field&lt;/span&gt;&lt;span class="s2"&gt;": "&lt;/span&gt;&lt;span class="err"&gt;kwh&lt;/span&gt;&lt;span class="s2"&gt;"
}]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we deploy a new aggregation file, Firestore would automatically create those aggregations and watch for changes in the specified documents/fields to update the aggregation - similar to how we'd do this using Cloud Functions but automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Detailed usage logs
&lt;/h2&gt;

&lt;p&gt;Sometime ago I ran into an issue where my Firestore bill had a spike but I didn't see an increase in traffic. Debugging that was really painful. The spike was coming from the number of reads. Initially, I thought I was suffering an attack (we didn't have &lt;a href="https://firebase.google.com/docs/app-check" rel="noopener noreferrer"&gt;AppCheck&lt;/a&gt; back then) but that wasn't the case. It turns out, I had a Cloud Function that was reading (extremely) often from a collection.&lt;/p&gt;

&lt;p&gt;My life would have been much easier if Firebase would provide detailed logs of reads/writes/deletes for each document. &lt;a href="https://cloud.google.com/firestore/docs/key-visualizer" rel="noopener noreferrer"&gt;Key Visualizer&lt;/a&gt; may help with this nowadays but it would be amazing to have easily accessible logs from the Firebase Console. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to the "Usage" tab on Firestore.&lt;/li&gt;
&lt;li&gt;Select "Reads", "Writes", or "Deletes".&lt;/li&gt;
&lt;li&gt;Group by "collections" or "documents".&lt;/li&gt;
&lt;li&gt;Order then by ascending or descending.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Joins using references
&lt;/h2&gt;

&lt;p&gt;Firestore has a &lt;a href="https://firebase.google.com/docs/firestore/manage-data/data-types#data_types" rel="noopener noreferrer"&gt;Reference data type&lt;/a&gt; but it isn't very useful at the moment. I wish we could use References to automatically have joins.&lt;/p&gt;

&lt;p&gt;For example, let's say we create the following post in our database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;whatever&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;serverTimestamp&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello 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;p&gt;I know Firebase isn't a relational database but I wish it would automatically create a relation between the &lt;code&gt;author&lt;/code&gt; field and its reference. So, when I'd fetch this post, I could do &lt;code&gt;author.name&lt;/code&gt; to get the author name, for example.&lt;/p&gt;

&lt;p&gt;We can do this using Cloud Functions and adding a &lt;code&gt;authorData&lt;/code&gt; field and keep it in sync with &lt;code&gt;author/{authorID}&lt;/code&gt; but it would amazing if Firestore would automatically do this for us.&lt;/p&gt;

&lt;p&gt;In their backend, they could have a trigger that automatically fetches the author document when the post is created and they could automatically update the author data when &lt;code&gt;users/{userID}&lt;/code&gt; changes. This way our queries would still be fast and cheap but we wouldn't have to handle this logic by ourselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Automatic backups for Firestore
&lt;/h2&gt;

&lt;p&gt;The official &lt;a href="https://firebase.google.com/docs/firestore/solutions/schedule-export" rel="noopener noreferrer"&gt;Firestore documentation has a tutorial&lt;/a&gt; on how to backup your Firestore database periodically. But I wish they would do backups automatically, at least on a daily basis, and provide an one-click option in the console to restore (or download) a backup.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Data migration
&lt;/h2&gt;

&lt;p&gt;Doing data migrations on Firestore can be painful. I wish Firebase would provide an official tool for handling that. Something that we could call from the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a new /migrations/2022_01_20_10_28_54-rename-post-author.js file&lt;/span&gt;
firebase migrate new rename-post-author

&lt;span class="c"&gt;# Run pending migrations&lt;/span&gt;
firebase migrate

&lt;span class="c"&gt;# Run a specific migration&lt;/span&gt;
firebase migrate up 2022_01_20_10_28_54-rename-post-author.ts

&lt;span class="c"&gt;# Undo a migration&lt;/span&gt;
firebase migrate down 2022_01_20_10_28_54-rename-post-author.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Migration files would need to have &lt;code&gt;up&lt;/code&gt; and &lt;code&gt;down&lt;/code&gt; functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postsRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;up&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;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;postsRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Rename post author for each post&lt;/span&gt;
  &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&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;updates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;deleteField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&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;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updates&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;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&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;down&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;
  
  
  7. Migration branches
&lt;/h2&gt;

&lt;p&gt;Databases such as &lt;a href="https://planetscale.com/" rel="noopener noreferrer"&gt;PlanetScale&lt;/a&gt; and &lt;a href="https://www.xata.io/" rel="noopener noreferrer"&gt;Xata&lt;/a&gt; are using version control for data migrations. It's easier to update your data structure without breaking anything in production. I wish Firebase would have that feature too.&lt;/p&gt;

&lt;p&gt;We could use the Firebase Migrate tool I suggested on the item above to create new branches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run the migration on a new branch&lt;/span&gt;
firebase migrate &lt;span class="nt"&gt;-b&lt;/span&gt; rename-post-author

&lt;span class="c"&gt;# Create a merge request to merge our feature branch into main &lt;/span&gt;
firebase migrate &lt;span class="nt"&gt;-mr&lt;/span&gt; rename-post-author main

&lt;span class="c"&gt;# Interactive panel to list all merge requests and merge them&lt;/span&gt;
firebase migrate &lt;span class="nt"&gt;-lmr&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  8. SSR Hosting deployed to the edge
&lt;/h2&gt;

&lt;p&gt;Hosting services like &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;, &lt;a href="https://fly.io/" rel="noopener noreferrer"&gt;Fly.io&lt;/a&gt;, and &lt;a href="https://workers.cloudflare.com/" rel="noopener noreferrer"&gt;CloudFlare Workers&lt;/a&gt; allow you to deploy both static files and server-side code close to your users (the edge). I wish Firebase Hosting would also allow us to do it.&lt;/p&gt;

&lt;p&gt;Right now, Firebase Hosting only allows us to deploy static files. If we need server-side rendering, then we need to use Cloud Functions on top of it, which adds complexity and it has the cold starts issue.&lt;/p&gt;

&lt;p&gt;I wish Firebase Hosting would allow us to deploy server-side code to the edge without using Cloud Functions. It would be another step to use only Firebase for a full-stack application.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Custom error messages for Security Rules
&lt;/h2&gt;

&lt;p&gt;When a security rule fails, we get a &lt;code&gt;Permission denied&lt;/code&gt; error from Firestore. I remember reading somewhere they do this for security rules because they don't want users to know which security rule failed.&lt;/p&gt;

&lt;p&gt;However, sometimes it's useful to show users a specific validation failed. I wish Firebase would allow us to write custom error messages for each security rule. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;match /posts/{postID} {&lt;/span&gt;
  &lt;span class="s"&gt;allow create&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;if&lt;/span&gt;
    &lt;span class="s"&gt;custom({&lt;/span&gt;
      &lt;span class="s"&gt;rule&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;request.resource.data.description.size() &amp;lt; 1000,&lt;/span&gt;
      &lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;post&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;needs&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;have&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;less&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;than&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1000&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;characters.'&lt;/span&gt;
    &lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would allow us to use fewer client-side validations and rely on the error messages coming from the backend (Firestore).&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Deploy Firestore to the edge
&lt;/h2&gt;

&lt;p&gt;When we create a new Firebase project, we need to choose which region our GCP resources will live in. That includes where our Firestore database will be located. That's not ideal for global services because some requests will be far away from users, adding latency.&lt;/p&gt;

&lt;p&gt;It would be amazing if Firestore would be deployed to the edge (close to users).&lt;/p&gt;

&lt;h2&gt;
  
  
  11. JSON import/export for the Firestore Emulator
&lt;/h2&gt;

&lt;p&gt;The Firestore Emulator allows us to import/export but it does so in its own format. Sometimes, it's useful to fetch some initial data (ex. working locally, testing, etc.). It would be great if the &lt;code&gt;--import&lt;/code&gt; flag could accept a &lt;code&gt;json&lt;/code&gt; file to import the data.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Support indexes in the emulator
&lt;/h2&gt;

&lt;p&gt;I wish I could use more the Firebase Emulator but it's hard to trust it when there are unsupported features such as indexes. I wish Firebase would pay more attention to this and only release new features when it's also available in the emulator.&lt;/p&gt;

&lt;p&gt;Otherwise, the emulator doesn't give us confidence that things will be working in the same way as they will in production. I had an issue a few times where I worked on a feature and only found out it was broken after it was already merged to our &lt;code&gt;staging&lt;/code&gt; environment where I'd get a &lt;code&gt;missing index&lt;/code&gt; error and I'd need to open a new PR to fix it.&lt;/p&gt;

&lt;p&gt;There's an &lt;a href="https://github.com/firebase/firebase-tools/issues/2027" rel="noopener noreferrer"&gt;open issue for this on GitHub&lt;/a&gt;, though.&lt;/p&gt;

&lt;h2&gt;
  
  
  13. Self-hosted / open-source
&lt;/h2&gt;

&lt;p&gt;Even though I love using Firebase, it's hard to convince larger teams to use it. The main issue people argue about is because Firebase isn't open-source and it would lock us into their product.&lt;/p&gt;

&lt;p&gt;If Firebase products were open-sourced and allow us to self-host them, it would be much easier to convince larger teams (especially in enterprise) to adopt their products. Most of the time, I don't even think we would self-host Firebase but making it open-source adds transparency to the process and it's an ensure that we could do it in case things change or Google eventually shuts Firebase down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional notes
&lt;/h2&gt;

&lt;p&gt;I know there are workarounds to implement some of the functionality above but the user experience isn’t great and they require a lot of work. I wish Firebase would become a backend-as-a-service that allows you to not worry about none of the things above, so you can focus on your product only.&lt;/p&gt;

&lt;p&gt;I also know some of those features are best handled by SQL databases but, then, SQL comes with its own challenges (don't need to do SQLsplaining to me). Some of them are being resolved by products like &lt;a href="https://planetscale.com/" rel="noopener noreferrer"&gt;PlanetScale&lt;/a&gt; and &lt;a href="https://www.xata.io/" rel="noopener noreferrer"&gt;Xata&lt;/a&gt;, though. I think the product that manages to solve those issues will have a huge business in their hands, especially with the rise of &lt;a href="https://jamstack.wtf/" rel="noopener noreferrer"&gt;Jamstack&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://twitter.com/ceolinwill" rel="noopener noreferrer"&gt;Follow me on Twitter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>serverless</category>
      <category>jamstack</category>
      <category>googlecloud</category>
    </item>
  </channel>
</rss>
