<?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: Denis Anisimov</title>
    <description>The latest articles on DEV Community by Denis Anisimov (@dbanisimov).</description>
    <link>https://dev.to/dbanisimov</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%2F143609%2F91df7acb-29ba-45f8-8b2f-e983537ec1e5.jpg</url>
      <title>DEV Community: Denis Anisimov</title>
      <link>https://dev.to/dbanisimov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dbanisimov"/>
    <language>en</language>
    <item>
      <title>Lighter font weight in code editors - yay or nay?</title>
      <dc:creator>Denis Anisimov</dc:creator>
      <pubDate>Tue, 29 Sep 2020 13:52:34 +0000</pubDate>
      <link>https://dev.to/dbanisimov/lighter-font-weight-in-code-editors-yay-or-nay-271l</link>
      <guid>https://dev.to/dbanisimov/lighter-font-weight-in-code-editors-yay-or-nay-271l</guid>
      <description>&lt;p&gt;Many code-oriented monospaced fonts looks 'bold' by default compared to your regular fonts. I've recently switched to &lt;code&gt;Cascadia Mono SemiLight&lt;/code&gt; and kind of dig it, it feels easier to pick up different code tokens than with the regular weight.&lt;/p&gt;

&lt;p&gt;Anyone else is using lighter versions of this or other fonts?&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>Share a thing you've been working on lately</title>
      <dc:creator>Denis Anisimov</dc:creator>
      <pubDate>Sun, 23 Feb 2020 19:19:07 +0000</pubDate>
      <link>https://dev.to/dbanisimov/share-a-thing-you-ve-been-working-on-lately-2n9c</link>
      <guid>https://dev.to/dbanisimov/share-a-thing-you-ve-been-working-on-lately-2n9c</guid>
      <description>&lt;p&gt;What's something you've been building, researching or tinkering with lately?&lt;/p&gt;

&lt;p&gt;It doesn't have to be prime-time ready, just share it here to get some encouragement from the community🤗&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>Hiring without interviews - is it possible?</title>
      <dc:creator>Denis Anisimov</dc:creator>
      <pubDate>Thu, 20 Feb 2020 23:14:51 +0000</pubDate>
      <link>https://dev.to/dbanisimov/hiring-without-interviews-is-it-possible-43n0</link>
      <guid>https://dev.to/dbanisimov/hiring-without-interviews-is-it-possible-43n0</guid>
      <description>&lt;p&gt;Hatred towards an interviewing process in the tech field is a common topic, but if you were to design the hiring process from scratch how would you do it?&lt;/p&gt;

&lt;p&gt;Should we keep doing the standardized repeatable whiteboard-style interviews?&lt;/p&gt;

&lt;p&gt;Or should it be a take-home assignment? Paid or not?&lt;/p&gt;

&lt;p&gt;Or maybe we should move to the model of already vetted candidates where you just pick and hire?&lt;/p&gt;

&lt;p&gt;I'm interested in the perspective from both sides - the companies and the candidates.&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>Building Image CDN with Firebase</title>
      <dc:creator>Denis Anisimov</dc:creator>
      <pubDate>Sun, 10 Nov 2019 23:58:26 +0000</pubDate>
      <link>https://dev.to/dbanisimov/building-image-cdn-with-firebase-15ef</link>
      <guid>https://dev.to/dbanisimov/building-image-cdn-with-firebase-15ef</guid>
      <description>&lt;p&gt;Delivering a perfect image on the web is not that easy of a task as it seems. Long gone the days where you can just put 'image.jpg' in your '/var/www/dist' and call it a day. You want your website to load images quickly, in the right format and with a perfect size based on the user's device. And you want that without blowing up your cloud storage bills and ideally without tedious work of resizing and tweaking your images manually.&lt;/p&gt;

&lt;p&gt;Luckily for us Image CDNs exist. These are specialized types of Content Delivery Networks (CDN) that can take a source image, perform the conversions and transformations on the fly and cache the result to be delivered globally in a matter of milliseconds. Some examples are &lt;a href="https://cloudinary.com/" rel="noopener noreferrer"&gt;Cloudinary&lt;/a&gt; and &lt;a href="https://www.imgix.com/" rel="noopener noreferrer"&gt;imgix&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this post we will build our own image CDN - efficient, fast, customizable, living on our own domain, and free using Firebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we are going to build
&lt;/h2&gt;

&lt;p&gt;In a nutshell we want to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload an image &lt;code&gt;image.jpg&lt;/code&gt; to a storage bucket.&lt;/li&gt;
&lt;li&gt;Use a specially formatted URL to retrieve that image in the right size, e.g &lt;code&gt;/cdn/image/width=100,height=50/image.jpg&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;That URL should work fast.&lt;/li&gt;
&lt;li&gt;We want to get the image in the &lt;a href="https://developers.google.com/speed/webp" rel="noopener noreferrer"&gt;webp&lt;/a&gt; format if our browser supports that.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Overall architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ffqph3oir95hu4n5wk8o4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ffqph3oir95hu4n5wk8o4.png" alt="Firebase Image CDN Architecture Diagram"&gt;&lt;/a&gt;&lt;/p&gt;
Btw, this image is served from Cloudinary Image CDN



&lt;p&gt;Some important details about the proposed design:&lt;/p&gt;

&lt;h3&gt;
  
  
  Firebase Hosting
&lt;/h3&gt;

&lt;p&gt;The Firebase Hosting part is where the automatic caching magic happens. By design Hosting caches all the responses from Functions that have &lt;code&gt;Cache-control&lt;/code&gt; HTTP header set to &lt;code&gt;public&lt;/code&gt; with an appropriate expiration time. That means that subsequent requests to the same image size, even from different browsers, will be served super fast and without firing another Function execution which saves us money.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that Firebase Hosting rewrite rules allow us to co-host our image CDN on the same domain as our main website.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Firebase Function
&lt;/h3&gt;

&lt;p&gt;The Firebase Function contains the image processing logic. It is also responsible for setting the appropriate response HTTP headers for Firebase Hosting to cache processed images correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Firebase Storage
&lt;/h3&gt;

&lt;p&gt;The main storage for original images is Firebase Storage and we identify images by their key in the bucket. Using Firebase Storage is good for the speed of access and works on the free tier since we aren't making outbound requests from our functions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Browser
&lt;/h3&gt;

&lt;p&gt;Modern browsers that support webp format send a special &lt;code&gt;Accept&lt;/code&gt; HTTP header with a value &lt;code&gt;image/webp&lt;/code&gt; (at least Firefox and Chrome do that). We will use that to automatically detect the support and convert the image.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that since the desired image response depends on the HTTP header we also need to include it in the Function's response &lt;code&gt;Vary&lt;/code&gt; header. This ensures that Firebase Hosting cache won't result in serving webp images to the browsers that don't support it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The image transformation function
&lt;/h2&gt;

&lt;p&gt;For image processing we are going to use &lt;a href="https://github.com/lovell/sharp" rel="noopener noreferrer"&gt;sharp&lt;/a&gt; JavaScript library. This is super handy for us as we don't need to spawn any additional processes. Also sharp is faster than ImageMagic, so win-win.&lt;/p&gt;

&lt;p&gt;You can find the full source code &lt;a href="https://github.com/dbanisimov/firebase-image-cdn" rel="noopener noreferrer"&gt;here&lt;/a&gt;. But without a single listing that wouldn't be a serious DEV post, right? So here is the snippet of the function implementation:&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;// Full code is here: https://github.com/dbanisimov/firebase-image-cdn&lt;/span&gt;

&lt;span class="c1"&gt;// Run the image transformation on Http requests.&lt;/span&gt;
&lt;span class="c1"&gt;// To modify memory and CPU allowance use .runWith({...}) method&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;imageTransform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onRequest&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;request&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="o"&gt;=&amp;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;sourceUrl&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;options&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;optionsStr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sourceUrlStr&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tokenizeUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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;sourceUrl&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;sourceUrlStr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;optionsStr&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="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&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="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Modern browsers that support WebP format will send an appropriate Accept header&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;acceptHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&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;webpAccepted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;acceptHeader&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;acceptHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/webp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&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="c1"&gt;// If one of the dimensions is undefined the automatic sizing&lt;/span&gt;
  &lt;span class="c1"&gt;// preserving the aspect ratio will be applied&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sharp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cover&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="nf"&gt;webp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;force&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;webpAccepted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lossless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lossless&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Set cache control headers. This lets Firebase Hosting CDN to cache&lt;/span&gt;
  &lt;span class="c1"&gt;// the converted image and serve it from cache on subsequent requests.&lt;/span&gt;
  &lt;span class="c1"&gt;// We need to Vary on Accept header to correctly handle WebP support detection.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responsePipe&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="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;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`public, max-age=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cacheMaxAge&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="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;Vary&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;Accept&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 built-in node https works here&lt;/span&gt;
  &lt;span class="nx"&gt;https&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="nx"&gt;sourceUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responsePipe&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;And also &lt;code&gt;firebase.json&lt;/code&gt; rewrites section, which transparently forwards requests to the Function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
...
  "rewrites": [
    {
      "source": "/cdn/image/**",
      "function": "imageTransform"
    },
    {
      "source": "**",
      "destination": "/index.html"
    }
  ]
...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The end result
&lt;/h2&gt;

&lt;p&gt;You can see the live demo here: &lt;a href="https://fir-image-cdn.web.app/" rel="noopener noreferrer"&gt;https://fir-image-cdn.web.app/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The cat's photo is resized on-the-fly and cached, you can check that by opening it again in a private browsing mode - the result will be noticeably fast. Another way to verify that we are seeing content served by the CDN cache is to check for the &lt;code&gt;x-cache: HIT&lt;/code&gt; header in the response.&lt;/p&gt;

&lt;h2&gt;
  
  
  How fast is it?
&lt;/h2&gt;

&lt;p&gt;Cached responses - low &lt;strong&gt;10ms&lt;/strong&gt; for small images which should stay pretty consistent globally.&lt;/p&gt;

&lt;p&gt;Cache miss - &lt;strong&gt;500ms+&lt;/strong&gt; for small images, up to &lt;strong&gt;1s&lt;/strong&gt; for medium images and &lt;em&gt;seconds (!)&lt;/em&gt; for large images. Whom to blame here is an open question - quick experiment with changing the Firebase Function type to a faster one hasn't shown noticeable difference, so it may not be due to the compute power, but rather Functions outbound networking or CDN cache fill performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cold starts&lt;/strong&gt;. The first Function execution takes significantly longer than subsequent executions, which may affect the user's experience. One way to avoid that is to use &lt;a href="https://cloud.google.com/run/" rel="noopener noreferrer"&gt;Cloud Run&lt;/a&gt; containers, which allow concurrent requests to the same instance. The great thing is that Cloud Run is supported by Firebase Hosting as the request forwarding destination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Low cache hit ratio&lt;/strong&gt;. In our approach we vary the cached response based on the &lt;code&gt;Accept&lt;/code&gt; header in order to correctly support webp detection. Every browser may send different value for that header which may result in very low cache hit ratio. We may avoid that by disabling the automatic detection and request webp version explicitly. Modern browsers allow to do that with a combination of &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;source type="image/webp"&amp;gt;&lt;/code&gt; HTML elements.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much does it cost?
&lt;/h2&gt;

&lt;p&gt;Great news everyone! The combo described above works perfectly under the Firebase free Spark plan. You roughly get 5GB of stored source images and 100K image transformations per month. &lt;/p&gt;

&lt;p&gt;It also seems like the cached responses served by CDN count towards the total Hosting download allowance of 10GB per month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;If you want to take the self-hosted Image CDN idea to the next level then take a look at &lt;a href="https://thumbor.readthedocs.io/en/latest/index.html" rel="noopener noreferrer"&gt;Thumbor&lt;/a&gt;. It has a ton of features and could be easily run inside of a Cloud Run container.&lt;/p&gt;

&lt;p&gt;You may get better overall performance (and cost of operations) by manually combining Cloud CDN, Load Balancer, multi-regional Cloud Storage and Cloud Functions deployed in multiple regions. But should you?&lt;/p&gt;

&lt;h2&gt;
  
  
  What else to read
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/prototyp/optimizing-images-for-the-web-an-in-depth-guide-4j7d"&gt;Optimizing images for the web - an in-depth guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/image-cdns/" rel="noopener noreferrer"&gt;Use image CDNs to optimize images&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images" rel="noopener noreferrer"&gt;Responsive images&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Stay sharp!&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>serverless</category>
      <category>cloud</category>
      <category>cdn</category>
    </item>
    <item>
      <title>Implementing manual OAuth sign-in flow for Facebook and Google</title>
      <dc:creator>Denis Anisimov</dc:creator>
      <pubDate>Sat, 09 Nov 2019 19:53:16 +0000</pubDate>
      <link>https://dev.to/dbanisimov/implementing-manual-oauth-sign-in-flow-for-facebook-and-google-2g5m</link>
      <guid>https://dev.to/dbanisimov/implementing-manual-oauth-sign-in-flow-for-facebook-and-google-2g5m</guid>
      <description>&lt;p&gt;Our web app uses Facebook and Google social logins. We've implemented those using the recommended approach with JS SDKs. &lt;/p&gt;

&lt;p&gt;That was easy to do and works fine most of the time, but I've started to see many users facing issues with that when they access the web app using a private browsing mode or have other privacy-related settings turned on. Namely some browsers block the Facebook JS SDKs, and many block third-party cookies which breaks the default Google sign-in.&lt;/p&gt;

&lt;p&gt;Given that I've started thinking about implementing the OAuth flow manually completely skipping the JS SDKs. Seems like this will give the most robust results, it is better for user's privacy and can save some bandwidth on the initial page load.&lt;/p&gt;

&lt;p&gt;Have anyone followed the same route? Are there any concerns with this approach, e.g. about the security of SDK way vs the manual way?&lt;/p&gt;

&lt;p&gt;See also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/identity/protocols/OAuth2UserAgent"&gt;Google OAuth Flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/identity/sign-in/web/sign-in"&gt;Google JS SDK Sign-in&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow"&gt;Facebook OAuth Flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.facebook.com/docs/facebook-login/web"&gt;Facebook JS SDK Sign-in&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>Firestore Dataflow Illustrated and Measured</title>
      <dc:creator>Denis Anisimov</dc:creator>
      <pubDate>Mon, 25 Mar 2019 03:55:35 +0000</pubDate>
      <link>https://dev.to/dbanisimov/firestore-dataflow-illustrated-and-measured-4kdg</link>
      <guid>https://dev.to/dbanisimov/firestore-dataflow-illustrated-and-measured-4kdg</guid>
      <description>&lt;p&gt;Firestore is more sophisticated than just a managed DBaaS - it comes with a real-time updates support, client caching, offline persistence and background triggered cloud functions. All of these pieces can be used to create very dynamic and scalable apps with complex dataflows without the headache of managing the infrastructure.&lt;/p&gt;

&lt;p&gt;But how all these pieces fit together and how can they be used to achieve different user experiences? In this blog post I'm presenting my view of a typical app data flow and perform a little testing.&lt;/p&gt;

&lt;p&gt;No time to read? Play with the interactive version here: &lt;a href="https://firestore-dataflow-test.firebaseapp.com/" rel="noopener noreferrer"&gt;Firestore Dataflow Test&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  A typical Firestore app dataflow
&lt;/h1&gt;

&lt;p&gt;For this part I'm assuming that we are building an app (web or mobile) that uses Firestore directly through official SDKs with local caching enabled. Our app may also want to perform some background data processing of Firestore documents and write the results back to the Firestore to be displayed on the client. All communication is realtime, which means that we use snapshot listeners on the client and Firestore triggers for functions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An example may be a chat app where we fetch link previews in the background.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Take a look at this diagram and read below for the description&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fcejuxexw9jhlyis7bdfc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fcejuxexw9jhlyis7bdfc.png" alt="Firestore dataflow diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;TC ~15ms&lt;br&gt;
&lt;small&gt;From client write to client read from the cache&lt;/small&gt;
&lt;/h3&gt;

&lt;p&gt;The shortest dataflow path is through the local cache. The data written by the client is saved in the cache and immediately triggers a snapshot listener.&lt;/p&gt;

&lt;p&gt;You should care about this data path if you rely on the local cache to provide optimistic UI updates. &lt;/p&gt;

&lt;h3&gt;TDb ~200ms&lt;br&gt;
&lt;small&gt;From client write to client read from the database&lt;/small&gt;
&lt;/h3&gt;

&lt;p&gt;The default path is from the client to the database backend. All writes eventually go there producing the final document result that is streamed back to the client and all other clients listening to affected queries. This path performance is reliant on the quality of a client network connection.&lt;/p&gt;

&lt;p&gt;This is the data path of client-to-client realtime communication. It's also the one for transactions, as those needs to be committed to the backend.&lt;/p&gt;

&lt;h3&gt;TTr ~400ms&lt;br&gt;
&lt;small&gt;From firestore write to firestore update with the triggered function&lt;/small&gt;
&lt;/h3&gt;

&lt;p&gt;Once the data is written to Firestore a background function is triggered. This path is heavily affected by the cold start time and also by the regional locations of your functions and Firestore.&lt;/p&gt;

&lt;p&gt;This path is important if you're making data changes in response to document creation or update.&lt;/p&gt;

&lt;h3&gt;TU ~600ms&lt;br&gt;
&lt;small&gt;From client write to client read of the data updated with the triggered function&lt;/small&gt;
&lt;/h3&gt;

&lt;p&gt;Finally your updated data needs to reach back the client. Technically it is the same snapshot listening to document updates. For the user experience it is the longest path.&lt;/p&gt;

&lt;p&gt;Despite the path length it may be a good choice to do background data processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  A special case with a callable function
&lt;/h2&gt;

&lt;p&gt;Sometimes talking directly to Firestore is not an option - the operation is too computationally heavy, or it needs to access some sensitive data, or requires complex validation that cannot be done with security rules. A common approach is to move the logic from the client to a callable function. The obvious downside of this case is that you loose the offline capabilities and caching and need to implement optimistic UI separately. &lt;/p&gt;

&lt;p&gt;Let's look at this special case&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fan0jl7hjwdhu4gfazf13.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fan0jl7hjwdhu4gfazf13.png" alt="Diagram of the Firestore dataflow with a callable function"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;TCall ~200ms&lt;br&gt;
&lt;small&gt;From client request to response received&lt;/small&gt;
&lt;/h3&gt;

&lt;p&gt;The direct path is a callable cloud function request. The response may be anything, but it's a good idea to include the document being written to the database so it can be used for optimistic update on the client. This path is heavily affected by the cold start time and network latencies.&lt;/p&gt;

&lt;h3&gt;TDb ~250ms&lt;br&gt;
&lt;small&gt;From client request to client read from the database&lt;/small&gt;
&lt;/h3&gt;

&lt;p&gt;The indirect path through Firestore. The data written to Firestore by the function can be read back at the client and other client listening to affected queries. This is the simplest way to get the result back as you don't need to process the callable function response separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring the paths lengths
&lt;/h2&gt;

&lt;p&gt;The times above are the results of measurements I've made and present here to show the relative lengths of different paths. They are on the optimistic side of the spectrum, especially if you factor in cold start times and proximity to Google Cloud datacenters. All times are for round-trip data paths.&lt;/p&gt;

&lt;p&gt;I've written a simple web-based testbed to get those numbers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Github &lt;a href="https://github.com/dbanisimov/firestore-dataflow-test" rel="noopener noreferrer"&gt;dbanisimov / firestore-dataflow-test&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I encourage you to clone the code and deploy your own project to see the performance in your case. Your geographical location and regions of Firestore and Cloud Functions will change the numbers significantly. It also shows very vividly cold start delays, time to establish connection to Firestore for the first time, and some other artifacts. It uses HTM + Preact (look ma, no build!) for the web client.&lt;/p&gt;

&lt;p&gt;Feel free to play around with the live version of the testbed:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://firestore-dataflow-test.firebaseapp.com/" rel="noopener noreferrer"&gt;Firestore Dataflow Test&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both Firestore and Functions are in us-central.&lt;/p&gt;

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

&lt;p&gt;Of course, the two cases above don't cover all the possible combinations of data flow primitives. More complex systems can be built with inclusion of PubSub and Cloud Storage, as both of them are well integrated with Cloud Functions. And drawing some high-level pictures and running experiments before building the whole system may save from some surprises later.&lt;/p&gt;

&lt;p&gt;Happy flowing! :)&lt;/p&gt;

</description>
      <category>firebase</category>
    </item>
    <item>
      <title>Automatic Firestore Backups</title>
      <dc:creator>Denis Anisimov</dc:creator>
      <pubDate>Sat, 09 Mar 2019 21:55:30 +0000</pubDate>
      <link>https://dev.to/dbanisimov/automatic-firestore-backups-16bh</link>
      <guid>https://dev.to/dbanisimov/automatic-firestore-backups-16bh</guid>
      <description>&lt;p&gt;Cloud Firestore has an &lt;a href="https://firebase.google.com/docs/firestore/manage-data/export-import" rel="noopener noreferrer"&gt;export/import&lt;/a&gt; functionality using CLI and REST APIs that allows you to make simple backups to a Cloud Storage bucket. It basically looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud beta firestore &lt;span class="nb"&gt;export &lt;/span&gt;gs://BUCKET_NAME &lt;span class="nt"&gt;--project&lt;/span&gt; PROJECT_ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are as clunky as me and prone to messing up with database you'd probably want to have it backed up somewhat often. But unfortunately, even with Firestore in GA, there is no managed automatic backups option yet. Here is what my solution looked like for a while:&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F0a8tzlxd6gg3rzy300zs.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F0a8tzlxd6gg3rzy300zs.PNG" title="Backup reminders in Google Calendar" alt="Backup reminders in Google Calendar"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not very &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" rel="noopener noreferrer"&gt;DRY&lt;/a&gt;, is it? Here is a better way using only Google Cloud Platform services and a tiny bit of coding.&lt;/p&gt;

&lt;h1&gt;
  
  
  Overall idea
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;Cloud Scheduler issues a message to a Cloud PubSub topic periodically based on a cron schedule.&lt;/li&gt;
&lt;li&gt;Cloud Function is triggered by the message and executes a call to the Firestore REST API&lt;/li&gt;
&lt;li&gt;Firestore export API starts a long-running operation that saves a backup to a specified bucket&lt;/li&gt;
&lt;li&gt;Cloud Storage stores backups organized by timestamps&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Prerequisites
&lt;/h1&gt;

&lt;p&gt;This post assumes that you have your Firebase project set up, have &lt;code&gt;gcloud&lt;/code&gt; command-line tool installed and know your way around Cloud Console.&lt;/p&gt;

&lt;h1&gt;
  
  
  Cloud Scheduler
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://cloud.google.com/scheduler/" rel="noopener noreferrer"&gt;Cloud Scheduler&lt;/a&gt; is a very simple service by GCP, but nevertheless very useful and much awaited by the server-less crowds. With it it's finally possible to define simple cron-like jobs that can trigger HTTP functions or emit a PubSub message. For use with Cloud Functions a PubSub way is preferred, as HTTP jobs don't have any authentication.&lt;/p&gt;

&lt;p&gt;The following command creates a PubSub job scheduled at midnight everyday. The message body is not used in our case, so can be arbitrary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud scheduler &lt;span class="nb"&gt;jobs &lt;/span&gt;create         &lt;span class="se"&gt;\&lt;/span&gt;
    pubsub firestore-backup          &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--schedule&lt;/span&gt; &lt;span class="s2"&gt;"0 0 * * *"&lt;/span&gt;           &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--topic&lt;/span&gt; &lt;span class="s2"&gt;"firestore-backup-daily"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--message-body&lt;/span&gt; &lt;span class="s2"&gt;"scheduled"&lt;/span&gt;            
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you execute this command your job will run at the specified time.&lt;/p&gt;

&lt;h1&gt;
  
  
  Cloud Function
&lt;/h1&gt;

&lt;p&gt;The cloud function is &lt;a href="https://firebase.google.com/docs/functions/pubsub-events" rel="noopener noreferrer"&gt;triggered on PubSub&lt;/a&gt; message and makes a request to Firestore REST API. To make a request with proper authentication we need to have an OAuth access token. Luckily for us Cloud Functions have a default service account that can be easily used to generate a token.&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;functions&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;firebase-functions&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;firebase&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;firebase-admin&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;request&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;request-promise&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initializeApp&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;backupOnPubSub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pubsub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;firestore-backup-daily&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;onPublish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &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="c1"&gt;// Get bucket name from cloud functions config&lt;/span&gt;
  &lt;span class="c1"&gt;// Should be in the format 'gs://BUCKET_NAME'&lt;/span&gt;
  &lt;span class="c1"&gt;//&lt;/span&gt;
  &lt;span class="c1"&gt;// Can be set with the following command:&lt;/span&gt;
  &lt;span class="c1"&gt;//   firebase functions:config:set backup.bucket="gs://BUCKET_NAME" &lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;backup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Firebase/GCP project ID is available as an env variable&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;projectId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&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;GCLOUD_PROJECT&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;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Exporting firestore database in project &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; to bucket &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bucket&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="c1"&gt;// Use default service account to request OAuth access token to authenticate with REST API&lt;/span&gt;
  &lt;span class="c1"&gt;// Default service account must have an appropriate role assigned or&lt;/span&gt;
  &lt;span class="c1"&gt;// the request authentication will fail&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="p"&gt;}&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;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applicationDefault&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getAccessToken&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;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://firestore.googleapis.com/v1/projects/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/databases/(default):exportDocuments`&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;result&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;request&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bearer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;outputUriPrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// The returned operation name can be used to track the result of the long-running operation&lt;/span&gt;
  &lt;span class="c1"&gt;//   gcloud beta firestore operations describe "OPERATION_NAME"&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;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&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;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Export operation started &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="s2"&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;Don't forget to set the config and deploy&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;firebase functions:config:set backup.bucket&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gs://BUCKET_NAME"&lt;/span&gt;
firebase deploy &lt;span class="nt"&gt;--only&lt;/span&gt; functions:backupOnPubSub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Firestore REST API permissions
&lt;/h1&gt;

&lt;p&gt;Import/Export Rest API requires appropriate OAuth scopes to be enabled. You can achieve that by adding &lt;code&gt;Cloud Datastore Import Export Admin&lt;/code&gt; role to the default service account in your project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud projects add-iam-policy-binding PROJECT_ID &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--member&lt;/span&gt; serviceAccount:PROJECT_ID@appspot.gserviceaccount.com &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--role&lt;/span&gt; roles/datastore.importExportAdmin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Cloud Storage
&lt;/h1&gt;

&lt;p&gt;In case you don't have a bucket yet you should create one. I've found that Firestore import/export requires at least regional storage class, coldline or nearline won't work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gsutil mb gs://BUCKET_NAME/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Testing everything together
&lt;/h1&gt;

&lt;p&gt;Once everything is set up you can trigger the whole process from the &lt;a href="https://console.cloud.google.com/cloudscheduler" rel="noopener noreferrer"&gt;Cloud Scheduler console&lt;/a&gt; by clicking "Run now" button&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fm4hv55j0jk8c0d1zqalf.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fm4hv55j0jk8c0d1zqalf.PNG" title="Cloud Scheduler console" alt="Cloud Scheduler console"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check logs in the Firebase Console or Stackdriver&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fd1v9vozkp5cj3cy8ji7m.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fd1v9vozkp5cj3cy8ji7m.PNG" title="Backup function logs" alt="Backup function logs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check export operation status&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud beta firestore operations describe &lt;span class="s2"&gt;"projects/PROJECT_ID/databases/(default)/operations/ASA3NDEwOTg0NjExChp0bHVhZmVkBxJsYXJ0bmVjc3Utc2Jvai1uaW1kYRQKLRI"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;: &lt;span class="nb"&gt;true
&lt;/span&gt;metadata:
  &lt;span class="s1"&gt;'@type'&lt;/span&gt;: type.googleapis.com/google.firestore.admin.v1.ExportDocumentsMetadata
  endTime: &lt;span class="s1"&gt;'2019-03-09T21:04:39.263534Z'&lt;/span&gt;
  operationState: SUCCESSFUL
  outputUriPrefix: gs://PROJECT_ID.appspot.com/2019-03-09T21:04:32_85602
  progressBytes:
    completedWork: &lt;span class="s1"&gt;'5360'&lt;/span&gt;
    estimatedWork: &lt;span class="s1"&gt;'4160'&lt;/span&gt;
  progressDocuments:
    completedWork: &lt;span class="s1"&gt;'40'&lt;/span&gt;
    estimatedWork: &lt;span class="s1"&gt;'40'&lt;/span&gt;
  startTime: &lt;span class="s1"&gt;'2019-03-09T21:04:32.862729Z'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally check the backup in the bucket&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fnch8xx21dxchp0dti8pp.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fnch8xx21dxchp0dti8pp.PNG" title="Backup folder in the bucket" alt="Backup folder in the bucket"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Next steps
&lt;/h1&gt;

&lt;p&gt;Automatic backup is only useful if it's working reliably. It is a good idea to have monitoring and alerting for your backup operations.&lt;/p&gt;

&lt;p&gt;One way to do this is to save the last export operation to Firestore and schedule a job some time after to check the result of the long-running operation. If the results is not successful it can send an email or log an error that will trigger an alert through &lt;a href="https://cloud.google.com/error-reporting/" rel="noopener noreferrer"&gt;Stackdriver Error Reporting&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another improvement is enabling of automatic deletion of old backups after a certain time. This could be achieved with &lt;a href="https://cloud.google.com/storage/docs/lifecycle" rel="noopener noreferrer"&gt;lifecycle management&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Closing remarks
&lt;/h1&gt;

&lt;p&gt;Would love to hear you thoughts on this approach and how it could be improved.&lt;/p&gt;

&lt;p&gt;Happy coding! Make backups :)&lt;/p&gt;

</description>
      <category>firebase</category>
    </item>
  </channel>
</rss>
