<?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: Anvil</title>
    <description>The latest articles on DEV Community by Anvil (@anvilfoundry).</description>
    <link>https://dev.to/anvilfoundry</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%2Forganization%2Fprofile_image%2F4062%2F6956223e-8616-4058-9f8a-3ee7b11c6f09.png</url>
      <title>DEV Community: Anvil</title>
      <link>https://dev.to/anvilfoundry</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anvilfoundry"/>
    <language>en</language>
    <item>
      <title>Understanding The Styled System</title>
      <dc:creator>Anvil Engineering</dc:creator>
      <pubDate>Thu, 08 Jul 2021 22:43:44 +0000</pubDate>
      <link>https://dev.to/anvilfoundry/understanding-the-styled-system-4cdh</link>
      <guid>https://dev.to/anvilfoundry/understanding-the-styled-system-4cdh</guid>
      <description>&lt;p&gt;The landscape of CSS has dramatically changed over the years and nowadays you'll find many developers emphatically love CSS-in-JS. First-time CSS-in-JS users often recognize one huge benefit: "Hey, I don't have to toggle between CSS and JS files anymore? Sweet!" While that is an incredible time saver, today I'll be writing about the &lt;a href="https://styled-system.com/" rel="noopener noreferrer"&gt;Styled System&lt;/a&gt;, a popular framework for getting the most out of CSS-in-JS and saving you significantly more time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Power of CSS-in-JS
&lt;/h2&gt;

&lt;p&gt;If you are already familiar with CSS-in-JS, you can skip this section. I will be recapping its capabilities and highlighting the most important features.&lt;br&gt;
At Anvil, we use &lt;a href="https://styled-components.com/" rel="noopener noreferrer"&gt;styled components&lt;/a&gt; in our &lt;a href="https://reactjs.org/" rel="noopener noreferrer"&gt;React&lt;/a&gt; applications. From this point on, I will use 'CSS-in-JS' and 'styled components' interchangeably and all examples will be with the &lt;code&gt;styled-components&lt;/code&gt; library.&lt;/p&gt;

&lt;p&gt;By bringing CSS to JS we not only save files written, but add all the dynamic capabilities of JavaScript to our styles. During your build step, your styled components will be compiled down into an optimized JavaScript bundle (or bundles, if you have multiple configured) and regular CSS classes directly in the head of your HTML. No CSS stylesheets whatsoever, just optimized CSS ready to go in a &lt;code&gt;style&lt;/code&gt; tag where needed.&lt;/p&gt;

&lt;p&gt;Here is an example of how to write a centered, blue box with styled-components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;styled&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;styled-components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// definition&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BlueBlox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="s2"&gt;`
    width: 300px;
    height: 300px;
    margin: 0 auto;
    background: blue;
`&lt;/span&gt;

&lt;span class="c1"&gt;// usage&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BlueBox&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="nx"&gt;am&lt;/span&gt; &lt;span class="nx"&gt;some&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="nx"&gt;within&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;blue&lt;/span&gt; &lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;BlueBox&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example is &lt;em&gt;literally&lt;/em&gt; CSS embedded into JavaScript. Pretty neat, but what if we want multiple boxes of different colors? In regular CSS land, we would have to create a different class for each color… kinda lame to repeat yourself right? Let's use JavaScript and &lt;strong&gt;dynamic props&lt;/strong&gt; to optimize our styled component.&lt;/p&gt;

&lt;p&gt;Since the color is going to change, our &lt;code&gt;BlueBox&lt;/code&gt; component doesn't make sense. Let's rename it to &lt;code&gt;ColoredBox&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;styled&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;styled-components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// definition&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ColoredBox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="s2"&gt;`
    width: 300px;
    height: 300px;
    margin: 0 auto;
    background: &lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;;
`&lt;/span&gt;

&lt;span class="c1"&gt;// usage&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ColoredBox&lt;/span&gt; &lt;span class="nx"&gt;backgroundColor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="nx"&gt;blue&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="nx"&gt;am&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;original&lt;/span&gt; &lt;span class="nx"&gt;blue&lt;/span&gt; &lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;ColoredBox&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ColoredBox&lt;/span&gt; &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="na"&gt;red&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;I am a new box, my background is red&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ColoredBox&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ColoredBox&lt;/span&gt; &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="na"&gt;yellow&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Primary colors ftw! Yellow is here as well.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ColoredBox&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much better than our old &lt;code&gt;BlueBox&lt;/code&gt; component. By passing a property of &lt;code&gt;backgroundColor&lt;/code&gt;, we are able to easily write different styles of boxes, never worrying about writing new CSS. This power extends to any imaginable CSS out there; you can pass hex values, rgba values, hsl values, etc. to our &lt;code&gt;backgroundColor&lt;/code&gt; prop and it will work. I won't go into detail here in this recap, but you can pass &lt;em&gt;entire style objects&lt;/em&gt; with multiple CSS properties dynamically as well. Check out one of my personal favorites from styled-components, the &lt;a href="https://styled-components.com/docs/api#css" rel="noopener noreferrer"&gt;css mixin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now that we have the syntax of CSS-in-JS fresh in our minds, let's dig into why we're here: &lt;a href="https://styled-system.com/" rel="noopener noreferrer"&gt;the Styled System&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Styled System
&lt;/h2&gt;

&lt;p&gt;As the name suggests, the Styled System employs a systematic approach to building components. The systematic approach comes in the form of enabling common style use cases directly in JavaScript. In the above example we saw how to dynamically set the background color using styled-components; imagine a world where you didn't have to write any CSS at all and could still achieve the same result.&lt;/p&gt;

&lt;p&gt;That is precisely what the Styled System does: it provides style props that you use when writing JavaScript that take care of all the CSS mess for you. Let's see how to implement our &lt;code&gt;ColoredBox&lt;/code&gt; component using the style props from the Styled System.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;styled&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;styled-components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;space&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;styled-system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// definition&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ColoredBox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;
  &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;// usage&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ColoredBox&lt;/span&gt; &lt;span class="nx"&gt;bg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yellow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0 auto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Styled&lt;/span&gt; &lt;span class="nx"&gt;System&lt;/span&gt; &lt;span class="nx"&gt;rocks&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;ColoredBox&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are the two yellow boxes, for reference:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;ColoredBox without the Styled System:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9s9iruwtoaz6n5ag61j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9s9iruwtoaz6n5ag61j.png" alt="ColoredBox without the Styled System"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ColoredBox with the Styled System:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fibgz3g9t1huhgt2pbo50.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fibgz3g9t1huhgt2pbo50.png" alt="ColoredBox with the Styled System"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Same result, but with our style props we get an almost one-line solution. Let's recap what we did:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;imported the appropriate objects from styled-system (&lt;code&gt;color&lt;/code&gt;, &lt;code&gt;layout&lt;/code&gt;, and &lt;code&gt;space&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;defined a styled component (&lt;code&gt;ColoredBox&lt;/code&gt;) using the style props&lt;/li&gt;
&lt;li&gt;used our component in the exact way we want to. Repeat this step for any imaginable &lt;code&gt;ColoredBox&lt;/code&gt;, whenever and however you want!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By using the Styled System, all of the specifics are pushed to the time we actually need to specify them, e.g. when actually using the component.&lt;/p&gt;

&lt;h3&gt;
  
  
  Abbreviated Style Props
&lt;/h3&gt;

&lt;p&gt;You'll also notice I used &lt;code&gt;bg&lt;/code&gt; for background color and &lt;code&gt;m&lt;/code&gt; for margin in the above example. A secondary benefit of the Styled System are abbreviations like these, plus a few other helpful ones. It isn't the biggest time saver, but typing &lt;code&gt;bg&lt;/code&gt; versus &lt;code&gt;background&lt;/code&gt; or &lt;code&gt;background-color&lt;/code&gt; every time is definitely easier on the fingers. My personal favorites from this convention are horizontal and vertical spacing.&lt;/p&gt;

&lt;p&gt;Along with &lt;code&gt;m&lt;/code&gt; for margin, you get &lt;code&gt;p&lt;/code&gt; for padding. Similar shorthands exist for each direction, so for padding you get: &lt;code&gt;pt&lt;/code&gt; for padding-top, &lt;code&gt;pb&lt;/code&gt; for padding-bottom, &lt;code&gt;pl&lt;/code&gt; for padding-left, and &lt;code&gt;pr&lt;/code&gt; for padding-right. Horizontally, you could define &lt;code&gt;pl&lt;/code&gt; and &lt;code&gt;pr&lt;/code&gt;, but why waste time writing both? Use &lt;code&gt;px&lt;/code&gt;, and the Styled System will apply your horizontal padding for you to padding-left and padding-right. Pretty sweet bonus, since there's no native way to do a one liner for just left and right spacing. This applies to all spacing properties, and there is indeed a &lt;code&gt;py&lt;/code&gt; for vertical spacing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of The Styled System
&lt;/h2&gt;

&lt;p&gt;By using this framework and its pattern, you unlock a multitude of benefits that it supports. You can read more about the benefits of the Styled System &lt;a href="https://styled-system.com/" rel="noopener noreferrer"&gt;here&lt;/a&gt;, but the two we will focus on throughout this blog post are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Style props that pick up values from a global theme&lt;/li&gt;
&lt;li&gt;Quickly set responsive font-size, margin, padding, width, and more&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Theming
&lt;/h3&gt;

&lt;p&gt;We have seen how the Styled System provides style props for us, allowing dynamic styling. But where should the actual style values reside? The answer is up to you and truly depends on your needs; if you are doing a side project by yourself, starting out with hardcoded values is a perfectly valid option.&lt;/p&gt;

&lt;p&gt;Where things get complicated is when you have a team of people building a real product. What shade of red are we using for the logo? What shade of red are we using for a button to indicate a dangerous action? Two very different scenarios, but very easily confused if using hardcoded values.&lt;/p&gt;

&lt;p&gt;Styled System solves this problem by providing a way to theme your application. It uses object notation via the &lt;a href="https://system-ui.com/theme/" rel="noopener noreferrer"&gt;System UI Theme Specification&lt;/a&gt;. This theme object falls under an age-old information architecture adage: &lt;a href="https://whatis.techtarget.com/definition/single-source-of-truth-SSOT" rel="noopener noreferrer"&gt;Single Source of Truth&lt;/a&gt;. By adhering to a theme, you are enforcing consistency across all components and pages of your application, while also enabling easy swapping of values. Think of all the 'dark mode' web apps and sites out there; each has some notion of theming, in which style values are swapped based on what theme is chosen. For each of those sites, the components themselves don't change, but the style values do.&lt;/p&gt;

&lt;p&gt;The System UI Theme Specification works primarily on the idea of &lt;strong&gt;scales&lt;/strong&gt;. Spacing and typography are defined using arrays in which each element is incremented by the same amount. For open-ended values like colors, an &lt;strong&gt;object&lt;/strong&gt; is used to define values. And lastly, the keys in our theme object correspond to the pluralized, camel-cased form of the underlying CSS property, e.g. &lt;code&gt;color&lt;/code&gt; will be &lt;code&gt;colors&lt;/code&gt;, &lt;code&gt;font-size&lt;/code&gt; will be &lt;code&gt;fontSizes&lt;/code&gt;, and so on. The only exception is the &lt;code&gt;space&lt;/code&gt; key, which represents &lt;em&gt;all space properties&lt;/em&gt;, including all margin and padding variants.&lt;/p&gt;

&lt;p&gt;With all that being said, everything is configurable. Besides the naming convention, your scales can be implemented however you want or you don't have to include a scale at all. Depending on how you actually use the theme, you might not even need to follow the naming conventions laid out in this article and on the System UI Theme Specification itself. But be warned, you miss lots of built-in benefits if you stray away from the convention!&lt;/p&gt;

&lt;p&gt;Here is an example of a complete theme:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;black&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#222222&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#00C0F2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;red&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#FF5C5C&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;yellow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#F8BF95&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;colors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;blacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#fafafa&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#d8d8d8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#bbb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#999&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#7a7a7a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#525252&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#3d3d3d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;black&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;blues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#F4F6F9&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#def3f7&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#00ABD7&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="na"&gt;reds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;red&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;yellows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;yellow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;black&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;red&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;yellow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;black&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;space&lt;/span&gt; &lt;span class="o"&gt;=&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;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&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;fontSizes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&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;fontWeights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;light&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;thin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;normal&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="na"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;black&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;900&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;fontStack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Comic Sans, -apple-system, BlinkMacSystemFont, sans-serif&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fontFamilies&lt;/span&gt; &lt;span class="o"&gt;=&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="nx"&gt;fontStack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fontStack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fontSizes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fontWeights&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fontFamilies&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;h4&gt;
  
  
  ThemeProviders
&lt;/h4&gt;

&lt;p&gt;Of the many ways to use a theme object, using a &lt;code&gt;ThemeProvider&lt;/code&gt; (if one is provided by your CSS-in-JS library) is the best way to go. It leverages the &lt;a href="https://reactjs.org/docs/context.html" rel="noopener noreferrer"&gt;React context API&lt;/a&gt; to pass down your theme values to all components of your application.&lt;/p&gt;

&lt;p&gt;Taking the example from &lt;a href="https://styled-system.com/#theming" rel="noopener noreferrer"&gt;Styled System&lt;/a&gt; itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// in App.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&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;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ThemeProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;styled-components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;theme&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;./theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ThemeProvider&lt;/span&gt; &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* application elements */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ThemeProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;


&lt;span class="c1"&gt;// in any other component, this will pick up 'black' and 'blue' from our theme!&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Box&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;black&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;bg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;Blue&lt;/span&gt; &lt;span class="nx"&gt;Box&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Box&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Element Variants
&lt;/h4&gt;

&lt;p&gt;Our last stop for theming an application is adding element variants to your theme. There currently exist 3 included element variants: &lt;code&gt;buttons&lt;/code&gt;, &lt;code&gt;textStyles&lt;/code&gt;, and &lt;code&gt;colorStyles&lt;/code&gt;. While we don't use element variants at Anvil, they are pretty sweet upgrades to base styles in your theme and can seriously help your style writing productivity.&lt;/p&gt;

&lt;p&gt;Element variants work by grouping any style you want to apply and assigning it to a key. Below is an example for &lt;code&gt;buttons&lt;/code&gt;; if you have used CSS frameworks like &lt;a href="https://getbootstrap.com/" rel="noopener noreferrer"&gt;Bootstrap&lt;/a&gt; before, these variants should feel familiar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// theme.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buttons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;white&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;secondary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;white&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;green&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;danger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;white&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;red&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// using a button variant&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'primary'&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Responsive Styling
&lt;/h3&gt;

&lt;p&gt;“Quickly set responsive font-size, margin, padding, width, and more” sounds a bit odd and hard to quantify. &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Responsive_Design" rel="noopener noreferrer"&gt;Responsive Design&lt;/a&gt; revolutionized the web, but how do you set that up quicker than a few media queries and use the &lt;code&gt;max-width&lt;/code&gt; property?&lt;/p&gt;

&lt;p&gt;The answer is by combining the previous section (theming your application) with defined breakpoints for your entire application.&lt;/p&gt;

&lt;p&gt;Similar to theming, defining breakpoints for your application provides consistency and better maintainability. I won't go into the nuances of responsive design and what are good breakpoints to set, but a 'breakpoint' is where your application's style changes based on the viewport width. For example, a widely accepted breakpoint is 769px for tablets; anything above that is considered wider than a tablet, so the next breakpoint's styles would apply.&lt;/p&gt;

&lt;p&gt;Writing media queries for each page or each component of your application gets tiring, &lt;em&gt;fast&lt;/em&gt;. The Styled System makes it a breeze by passing your breakpoints to a &lt;code&gt;ThemeProvider&lt;/code&gt;, which now gives all components created with Styled System the ability to take &lt;em&gt;arrays as values&lt;/em&gt; instead of their normal values. Each value in the array corresponds to the value that will be applied at each one of your specified breakpoints, which is incredibly easy to write compared to media queries.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// passed to ThemeProvider&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;breakpoints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;


&lt;span class="c1"&gt;// using breakpoints&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Box&lt;/span&gt;
  &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&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="c1"&gt;// 100% below the smallest breakpoint&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// 50% from the next breakpoint and up&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;   &lt;span class="c1"&gt;// 25% from the next breakpoint and up&lt;/span&gt;
  &lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="c1"&gt;// responsive font size&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Box&lt;/span&gt; &lt;span class="nx"&gt;fontSize&lt;/span&gt;&lt;span class="o"&gt;=&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="c1"&gt;// responsive margin&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Box&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="o"&gt;=&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="c1"&gt;// responsive padding&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Box&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;=&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of these &lt;code&gt;Box&lt;/code&gt;s will have media queries set up to do the responsive design for you, using a mobile-first approach. For the last three &lt;code&gt;Box&lt;/code&gt;s, there are 4 values despite 3 breakpoints; in this case, the last value is for any viewport width over the last breakpoint of 1000px.&lt;/p&gt;

&lt;h4&gt;
  
  
  Reflexbox
&lt;/h4&gt;

&lt;p&gt;At Anvil, we use responsive styling via the Styled System extensively. We use a component provider called &lt;a href="https://rebassjs.org/reflexbox/" rel="noopener noreferrer"&gt;Reflexbox&lt;/a&gt; by &lt;a href="https://rebassjs.org/" rel="noopener noreferrer"&gt;Rebass&lt;/a&gt;, which is an entire suite of prebuilt primitive components to use. Instead of reinventing the wheel, we utilize these components from Reflexbox to build our components with ease.&lt;/p&gt;

&lt;p&gt;I recommend you read the docs for Reflexbox, but fun fact--you already know how it works! There are 2 components built with the Styled System, &lt;code&gt;Box&lt;/code&gt; and &lt;code&gt;Flex&lt;/code&gt;. &lt;code&gt;Box&lt;/code&gt; takes the &lt;code&gt;layout&lt;/code&gt;, &lt;code&gt;space&lt;/code&gt;, &lt;code&gt;color&lt;/code&gt;, and &lt;code&gt;typography&lt;/code&gt; props, while &lt;code&gt;Flex&lt;/code&gt; has all of those plus &lt;code&gt;flexbox&lt;/code&gt; props. Both components support responsive styling as well 🤘🏼&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;In this blog post we covered the Styled System in-depth. If you are developing using the Styled System, you likely do not need to know such a granular level of detail, but like all things in software development, you tend to grok the technology the more you dig into it.&lt;/p&gt;

&lt;p&gt;We saw how CSS-in-JS enables much more than less source code files, but truly rapid component and style development. We also saw how theming and responsive styling streamline app development and provide consistency throughout entire applications, while enabling easy maintainability for style values.&lt;/p&gt;

&lt;p&gt;I'll leave you with one tidbit on the limitations of the Styled System: not all CSS values are supported. If you look into the &lt;code&gt;color&lt;/code&gt; prop source code, you'll find this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;system&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@styled-system/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;colors&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="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;backgroundColor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;colors&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="na"&gt;opacity&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="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backgroundColor&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;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks well and good, but &lt;code&gt;config.bg&lt;/code&gt; raises a question or two… We can use &lt;code&gt;bg&lt;/code&gt; on a component to set the &lt;code&gt;background-color&lt;/code&gt;, but how about &lt;code&gt;background-image&lt;/code&gt;? The CSS &lt;code&gt;background&lt;/code&gt; property is shorthand for 8 other properties (yes, 8!), yet only 1 is truly supported here. These are common things we can do in CSS, but the way this is implemented we can't do with the Styled System.&lt;/p&gt;

&lt;p&gt;Luckily for you and me, we can define our own &lt;a href="https://styled-system.com/custom-props" rel="noopener noreferrer"&gt;custom props&lt;/a&gt;. Quite an amazing system, if we can extend it like this!&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this blog post, and if you write your own custom props we'd love to hear about it. Send us a message at &lt;a href="//mailto:developers@useanvil.com"&gt;developers@useanvil.com&lt;/a&gt;, and we'll publish all the ones we get. Happy coding 🤘🏼&lt;/p&gt;

</description>
      <category>css</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Announcing the Anvil HTML to PDF API</title>
      <dc:creator>Anvil Engineering</dc:creator>
      <pubDate>Mon, 26 Apr 2021 18:33:34 +0000</pubDate>
      <link>https://dev.to/anvilfoundry/announcing-the-anvil-html-to-pdf-api-5b45</link>
      <guid>https://dev.to/anvilfoundry/announcing-the-anvil-html-to-pdf-api-5b45</guid>
      <description>&lt;p&gt;Recently we launched a &lt;a href="https://www.useanvil.com/developers"&gt;PDF API&lt;/a&gt; that included a &lt;a href="https://www.useanvil.com/docs/api/generate-pdf"&gt;PDF generation endpoint&lt;/a&gt;. The endpoint allows you to generate brand new PDFs with Markdown.&lt;/p&gt;

&lt;p&gt;Since launch, the most heavily requested API feature has been HTML to PDF. Developers said they want full control and they want generated PDFs to exactly match their brand.&lt;/p&gt;

&lt;p&gt;Today, I'm excited to announce that HTML to PDF support is available to all! Now, you can easily generate PDFs that look exactly the way you want, using your own colors, styles, and layout language.&lt;/p&gt;

&lt;p&gt;An &lt;a href="https://github.com/anvilco/html-pdf-invoice-template"&gt;example invoice&lt;/a&gt; PDF:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XSg7CrQd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hkrju3iyfhcku5p3e7r3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XSg7CrQd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hkrju3iyfhcku5p3e7r3.png" alt="HTML to PDF invoice"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a hurry? Check out the &lt;a href="https://www.useanvil.com/docs/api/generate-pdf#html--css-to-pdf"&gt;HTML to PDF docs&lt;/a&gt;, and our invoice example repo. &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i3JOwpme--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/anvilco"&gt;
        anvilco
      &lt;/a&gt; / &lt;a href="https://github.com/anvilco/html-pdf-invoice-template"&gt;
        html-pdf-invoice-template
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      An HTML invoice template for use in a browser and with HTML to PDF generation.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The HTML to PDF endpoint uses our existing PDF generation endpoint, but in addition to an array of objects, it accepts vanilla &lt;code&gt;html&lt;/code&gt; and &lt;code&gt;css&lt;/code&gt; strings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;POST&lt;/span&gt; &lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//app.useanvil.com/api/v1/generate-pdf&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&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="s2"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
      &amp;lt;h1 class='header-one'&amp;gt;What is Lorem Ipsum?&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;
        Lorem Ipsum is simply dummy text...
      &amp;lt;/p&amp;gt;
    `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
      body { font-size: 14px; color: #171717; }
      .header-one { text-decoration: underline; }
    `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's it! All the HTML and CSS you're used to can be sent up and rendered in PDF form.&lt;/p&gt;

&lt;p&gt;See the &lt;a href="https://www.useanvil.com/docs/api/generate-pdf#html--css-to-pdf"&gt;HTML to PDF docs&lt;/a&gt; for information on all the options you can pass to the &lt;code&gt;/api/v1/generate-pdf&lt;/code&gt; endpoint.&lt;/p&gt;
&lt;h2&gt;
  
  
  Using language-specific clients
&lt;/h2&gt;

&lt;p&gt;The new HTML to PDF features are also supported by our client libraries: &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i3JOwpme--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/anvilco"&gt;
        anvilco
      &lt;/a&gt; / &lt;a href="https://github.com/anvilco/node-anvil"&gt;
        node-anvil
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Node API Client for Anvil
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i3JOwpme--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/anvilco"&gt;
        anvilco
      &lt;/a&gt; / &lt;a href="https://github.com/anvilco/python-anvil"&gt;
        python-anvil
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Python library and CLI for the Anvil API
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;First you'll need an API key. &lt;a href="https://app.useanvil.com/signup"&gt;Sign up&lt;/a&gt; for Anvil, then get your &lt;a href="https://www.useanvil.com/docs/api/getting-started#api-key"&gt;API key&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's an example in node, replace &lt;code&gt;YOUR_API_KEY&lt;/code&gt; with your key, and HTML/CSS with your own code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&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;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Anvil&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@anvilco/anvil&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;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Anvil&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;YOUR_API_KEY&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;example&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;'&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;HTML Invoice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;html&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;h1&amp;gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;css&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body { ... } ...&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;statusCode&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;errors&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;generatePDF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invoice.pdf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;python_anvil.api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Anvil&lt;/span&gt;

&lt;span class="n"&gt;anvil&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Anvil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Example Invoice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"html"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;h1&amp;gt;..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"css"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"body { ... } ..."&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_pdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Beyond the browser
&lt;/h2&gt;

&lt;p&gt;Since your HTML &amp;amp; CSS will be rendered for a PDF instead of a browser, there may be special considerations you want to make given the medium. For example, page numbers don't make sense in a browser, but they do in a PDF.&lt;/p&gt;

&lt;p&gt;There are a few extra PDF-specific CSS features available that allow more control for PDF rendering. We'll go into more specifics in subsequent posts. For now, here are a couple of interesting features covered in our invoice example.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rendering in the margins
&lt;/h3&gt;

&lt;p&gt;Many PDFs have page numbering rendered on each page's margins. With the API, you can control what is rendered in the margin at each corner. In our &lt;a href="https://github.com/anvilco/html-pdf-invoice-template"&gt;invoice example&lt;/a&gt;, two items rendered are in the bottom margin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bottom left: an arbitrary element is rendered on the last page only&lt;/li&gt;
&lt;li&gt;Bottom right: numbering has been added to each page and styled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dWMnesVk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fqh8xuwun3ot65gusoa2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dWMnesVk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fqh8xuwun3ot65gusoa2.png" alt="HTML to PDF page numbering"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Repeating table headers
&lt;/h3&gt;

&lt;p&gt;In a browser, a table cannot overflow to another page, but in a PDF, it can! When a table overflows, by default the API will repeat the table's headers on the next page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AogSARnS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xc2xa2hnx0ese37zienz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AogSARnS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xc2xa2hnx0ese37zienz.png" alt="PDF Paginated table headers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  An example
&lt;/h2&gt;

&lt;p&gt;We've put together an &lt;a href="https://github.com/anvilco/html-pdf-invoice-template"&gt;example HTML/CSS invoice template&lt;/a&gt;, demonstrating the features mentioned above. It contains HTML and CSS that renders both in the browser and the API. Included is an &lt;a href="https://github.com/anvilco/html-pdf-invoice-template/blob/main/generate-pdf.js"&gt;example node script&lt;/a&gt; to help you quickly generate PDFs.&lt;/p&gt;

&lt;p&gt;Check it out to jumpstart your integration!&lt;/p&gt;

&lt;h2&gt;
  
  
  Onward
&lt;/h2&gt;

&lt;p&gt;At Anvil, we're on a mission to abstract away PDFs for developers. Hopefully this new HTML to PDF feature is a big step in making working with PDFs effortless.&lt;/p&gt;

&lt;p&gt;Lastly, if you’re developing something cool with PDFs and/or paperwork automation, we’d love to hear more from you! Let us know at &lt;a href="//mailto:developers@useanvil.com"&gt;developers@useanvil.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>html</category>
    </item>
    <item>
      <title>Introducing the Anvil API Python Library</title>
      <dc:creator>Anvil Engineering</dc:creator>
      <pubDate>Mon, 26 Apr 2021 04:46:06 +0000</pubDate>
      <link>https://dev.to/anvilfoundry/introducing-the-anvil-api-python-library-32fb</link>
      <guid>https://dev.to/anvilfoundry/introducing-the-anvil-api-python-library-32fb</guid>
      <description>&lt;p&gt;The Anvil API is now supported on Python via our official client library. The library is available now on &lt;a href="https://github.com/anvilco/python-anvil"&gt;GitHub&lt;/a&gt;:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i3JOwpme--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/anvilco"&gt;
        anvilco
      &lt;/a&gt; / &lt;a href="https://github.com/anvilco/python-anvil"&gt;
        python-anvil
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Python library and CLI for the Anvil API
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;and as a package through PyPI via &lt;code&gt;pip install python-anvil&lt;/code&gt; or your preferred package manager (i.e. pipenv, poetry).&lt;/p&gt;

&lt;p&gt;As a company, we are heavily invested in the Javascript ecosystem; however, we also realize that making our platform more developer-friendly and accessible to other developers means increasing our official API library footprint.  That’s why we’ve added Python to our client library as another step towards building a more developer-friendly Anvil. We hope this release gives developers an opportunity to enjoy all that Anvil has to offer.&lt;/p&gt;

&lt;p&gt;Features of this initial release:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keeping in line with current Javascript library, it includes:

&lt;ul&gt;
&lt;li&gt;Filling PDFs&lt;/li&gt;
&lt;li&gt;Generating PDFs&lt;/li&gt;
&lt;li&gt;Creating Etch Signing Packets&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;A command-line interface (CLI)

&lt;ul&gt;
&lt;li&gt;Contains the above, giving you the ability to test out our features in the Python REPL.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;anvil&lt;/code&gt; command from this package also has a few additional useful commands that will help you as you build out your integration, such as getting a list of PDF templates (or casts) for use in the “fill-pdf” API endpoint, as well as getting field data from a specific PDF template. All of this can be done from the comfort of your text-based terminal.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Filling PDFs
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.useanvil.com/docs/api/fill-pdf#filling-a-pdf-template"&gt;Filling PDFs&lt;/a&gt; lets you fill PDF templates created on Anvil with a simple API call.&lt;/p&gt;

&lt;p&gt;First, create a template on your Anvil dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uzdbXX_6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.useanvil.com/static/upload-ac23b30d51d5ecf54cc7f592d573b26a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uzdbXX_6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.useanvil.com/static/upload-ac23b30d51d5ecf54cc7f592d573b26a.png" alt="Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once uploaded, Anvil will attempt to find fields for you. You can also create your own fields and adjust any field types if you need something specific such as date formats.&lt;/p&gt;

&lt;p&gt;When you finish editing fields, click over to the API Info tab. It shows everything you need to fill the template with the API, including an example payload to quickly get started.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mdR5ryk9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.useanvil.com/static/pdf-template-api-info-c0fa393ffc6c8ac44a3c24a9f1624616.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mdR5ryk9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.useanvil.com/static/pdf-template-api-info-c0fa393ffc6c8ac44a3c24a9f1624616.png" alt="Template Info"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the data on the API Info page, you can now create your API call. The resulting &lt;code&gt;response&lt;/code&gt; will be the PDF file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;python_anvil.api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Anvil&lt;/span&gt;

&lt;span class="n"&gt;anvil&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Anvil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"MY API KEY"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"IRS W-4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"font_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"someFieldName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Example data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"aNumberField"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1234&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fill_pdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"template_id_here"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generating PDFs
&lt;/h3&gt;

&lt;p&gt;The Anvil API also allows you to &lt;a href="https://www.useanvil.com/docs/api/generate-pdf"&gt;generate new PDFs&lt;/a&gt; using provided JSON data. Useful for agreements, invoices, disclosures, or any other text-heavy documents.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;python_anvil.api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Anvil&lt;/span&gt;

&lt;span class="n"&gt;anvil&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Anvil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"MY API KEY"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Example Invoice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"font_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Supports **markdown**"&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="s"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"rows"&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="s"&gt;"Description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Quantity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Price"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"4x Large Widgets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"$40.00"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"10x Medium Sized Widgets in dark blue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"10"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"$100.00"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"6x Small Widgets in white"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"$60.00"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_pdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"template_id_here"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating Etch Signing Packets
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.useanvil.com/docs/api/e-signatures"&gt;Anvil Etch E-sign API&lt;/a&gt; allows you to collect e-signatures from within your app. Send a signature packet including multiple PDFs, images, and other uploads to one or more signers. Templatize your common PDFs. Then, fill them with your user's information before sending out the signature packet.&lt;/p&gt;

&lt;p&gt;This is one of the more complex methods in the API, so please take a look at the &lt;a href="https://python-anvil.readthedocs.io/en/latest/advanced/#create-etch-packet"&gt;Python Anvil docs&lt;/a&gt; for a closer look on the process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Command-line interface
&lt;/h3&gt;

&lt;p&gt;The included CLI gives you quick access to the methods above, as well as some additional GraphQL queries that can help with getting certain data without having to open up your browser.&lt;/p&gt;

&lt;p&gt;When running the &lt;code&gt;anvil&lt;/code&gt; command by itself, you'll be shown a display of support commands. Each individual command also has additional help documentation on how to use the command.&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;# The CLI commands will use the environment variable "ANVIL_API_KEY" for all&lt;/span&gt;
&lt;span class="c"&gt;# Anvil API requests.&lt;/span&gt;
&lt;span class="nv"&gt;$ ANVIL_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;MY_GENERATED_KEY anvil
Usage: anvil &lt;span class="o"&gt;[&lt;/span&gt;OPTIONS] COMMAND &lt;span class="o"&gt;[&lt;/span&gt;ARGS]...

Options:
  &lt;span class="nt"&gt;--debug&lt;/span&gt; / &lt;span class="nt"&gt;--no-debug&lt;/span&gt;
  &lt;span class="nt"&gt;--help&lt;/span&gt;                Show this message and exit.

Commands:
  cast                Fetch Cast data given a Cast eid.
  create-etch         Create an etch packet with a JSON file.
  current-user        Show details about your API user
  download-documents  Download etch documents
  fill-pdf            Fill PDF template with data
  generate-etch-url   Generate an etch url &lt;span class="k"&gt;for &lt;/span&gt;a signer
  generate-pdf        Generate a PDF
  weld                Fetch weld info or list of welds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, if you’re developing something cool with PDFs and/or paperwork automation, we’d love to hear more from you! Let us know at (email here) if you have any feedback. Also, if there are any suggestions on new features or issues you may encounter for this library, let us know on the Issue section of the GitHub repo page, and we’ll get back to you.&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>githunt</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Tackling the mighty PDF with Anvil</title>
      <dc:creator>Anvil Engineering</dc:creator>
      <pubDate>Mon, 26 Apr 2021 04:44:10 +0000</pubDate>
      <link>https://dev.to/anvilfoundry/tackling-the-mighty-pdf-with-anvil-3o0g</link>
      <guid>https://dev.to/anvilfoundry/tackling-the-mighty-pdf-with-anvil-3o0g</guid>
      <description>&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/523293771" width="710" height="399"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Full Transcript of video below - lightly edited for clarity
&lt;/h3&gt;

&lt;p&gt;Hello everyone. My name is Ben. Today's talk is about tackling the mighty PDF. So we'll start with a little bit of context about PDF and then we'll get into some code stuff. We’ll talk about the challenges of working with PDFs, some of the challenges that we faced and then I'll demo some things that hopefully make working with PDFs a lot easier.&lt;/p&gt;

&lt;p&gt;So co-founder CTO of Anvil, little bit of background, I have been a developer for a long time. Previous to Anvil, I worked at GitHub on the Atom editor and before that I founded a design tool startup.&lt;/p&gt;

&lt;p&gt;The PDF kind of needs no introduction, but I want to give like a little bit of context before we dive into the code things. So you've done this before, you've got an email from somebody with a PDF that's asking for all of your most personal information. So you fill it all out  and you mail it back, and you aren’t sure exactly where your data is going to end up. I know, not a lot of people like doing this, I sure don't but it's super common.&lt;/p&gt;

&lt;p&gt;Most email attachments are PDFs, like most non cat picture email attachments are PDFs, and they're everywhere! If you had to guess how many PDFs were out there, what would you guess? Would you say like a billion? Ha ha. Would that be exaggerating? Turns out that there's two and a half trillion PDFs. That's not necessarily unique PDFs, but it's a lot of PDFs.&lt;/p&gt;

&lt;p&gt;There's 20 billion PDFs  just in Dropbox.&lt;/p&gt;

&lt;p&gt;So there are some really good uses of PDFs aside from forms.&lt;/p&gt;

&lt;p&gt;Resumes, ebooks, we submitted these talk slides by PDF, academic papers and one of my favorite uses that I just learned about is fashion specs. So all of these have a couple things in common, what do they have in common? They are all for consumption and sharing,&lt;br&gt;
PDFs are really good for things that you don't need to edit, you're not editing the PDF. You're not filling something out in the PDF.&lt;/p&gt;

&lt;p&gt;All you're doing is you're reading them. So I think we can say that PDFs are really good for screens and for printers and this makes a lot of sense, because this is what PDFs were originally built for, they were built to display information exactly the same across all devices. Also be shareable.&lt;/p&gt;

&lt;p&gt;So that way you can send your underwear PDF to the underwear factory and get them made exactly like you want them to, be because they don't worry about them having fonts or not. It'll look exactly the same as the way that you intended.  &lt;/p&gt;

&lt;p&gt;But the most common way that people have interacted with PDFs, certainly for me, is by filling out PDF forms. PDF forms are really annoying for a couple of reasons. Yes, one is that there's a lot of repetition so you have to fill out your name six times or fill out several PDFs with the exact same information. There's also a lot of PDFs that have logic, so you have to read and understand part of the PDF, and then make decisions.&lt;/p&gt;

&lt;p&gt;So this is a 401k tax form. If you want to take money out of your 401k, there are complications, because it depends on your situation. So this is obviously  complicated right? It turns out that humans are really bad at this.&lt;/p&gt;

&lt;p&gt;They're really bad at filling out PDFs. A professional data entry person's error rate is 4% but I know that my error rate is a lot higher, unless I'm really really paying attention, and we've heard from our customers that their customers make a lot of mistakes.&lt;/p&gt;

&lt;p&gt;Yes, so it is  really good for screens and for printers, but maybe not so great for humans because filling out these forms is interesting.&lt;/p&gt;

&lt;p&gt;Okay. So what would be better than having humans do it? Computers are obviously a lot better than having a human fill it out. Computers are really good at logic and repetition. So it makes sense that they could think and sort of make these decisions we can ask the user. What's your name, and your birth date, and your address, and then we can basically with a couple of pieces of information fill out all of those check boxes and fill out all the right fields. This is what we want.&lt;/p&gt;

&lt;p&gt;We don't want people to be interacting with these forms. How do we abstract away of the PDF? We can't get rid of the PDF because there's two and a half trillion of them. And behavior change is really hard. But I think that we can make it so that no human has to deal with PDFs.&lt;/p&gt;

&lt;p&gt;All right, so I guess the question is how do we interact with PDFs as developers? Do we go and read the spec? Probably not. I don't think anyones gonna do that. I certainly haven't read all of the PDF spec or the HTML spec and I write a fair amount of HTML. It's huge. It's complicated. What do we do? We are going to find a library on the internet. When we were starting Anvil, this is exactly what we did, we went to the internet and we were  like, “PDF filling libraries”. What we learned is that developer attention to the technology is important. Some technologies have a lot of attention from developers, take web technologies.&lt;/p&gt;

&lt;p&gt;It was created sort for the same reasons as PDF for display and consumption and that kind of thing, for sharing academic papers. But since then it's had like insane developer adoption. There's literally millions of developers building things. Like for the web, on the web, they’re building  web libraries, there's people complaining  on issues, there's people fixing those bugs. And what all of this collaboration results in is a bunch of really well made, maintained, high level libraries. They have good doc's, you know, they're updated all the time. So in this world, you're never interacting with the DOM APIs, you're actually interacting with react, sass and typescript and all of these cool technologies that people have built on top of web tech. So as a developer,  I’ve become super accustomed to this kind of thing. I’ve got a problem. I can go to  GitHub, I can find a library that does mostly what I want.&lt;/p&gt;

&lt;p&gt;But PDF Tech isn't really there yet compared to web development. there's very few developers looking at it, so like every human has to deal with PDFs, but there aren't so many developers looking at this.&lt;/p&gt;

&lt;p&gt;All right, there are libraries though and they generally fall into two categories. They're complete or incomplete. So the complete libraries can do everything that you would maybe want to do with a PDF. You can read them, you can write them, you can update them. On the incomplete side, they usually just do like one tiny thing, and often these incomplete libraries aren't well-maintained, haven’t been touched in five years, you can also run headless Chrome to generate PDFs, but you can't update a PDF with Chrome&lt;/p&gt;

&lt;p&gt;So the complete side seems really good. Right? This is the route that we went down. But like the DOM APIs they're pretty low level. So you have to know a lot about the PDF and how its put together to read, edit, create them. So if you're saying you  want to do something simple, something seemingly simple like creating an invoice. That can lead to like a lot of overhead to set a whole thing up to create this invoice.&lt;/p&gt;

&lt;p&gt;Okay. So now I'm kind of into the code part of things, if we are working with low-level apis. If we are working with low level APIs and we have to know about these PDF details. What are some of the challenges? So I think you see some of the challenges that we face when we are building out our PDF service. And so these are some of the challenges. So number one is layout, if you want to create a new PDF or you want to add something to a PDF you have to deal with layout. Everything in a PDF is absolutely positioned, fixed sizing and there's no text wrapping. So there's not a lot of niceties for a content creator. This logic that would wrap things and whatever, is like pushed up to these from the application layer. So coming from HTML this might be a little weird. HTML is declarative. So it's like “here's what we want to display” and the browser will make decisions based on well, how wide is this screen? Like let me shrink it down and wrap stuff. You can also give the browser guidelines with CSS by saying “OK, I want to use flexbox and I want this column to be this wide”, but the browser might ignore you, the browser is making these decisions on how to display things.&lt;/p&gt;

&lt;p&gt;So the PDF is opposite that where you are specifying exactly how to draw a document. There's a bunch of little drawing commands in here saying okay. I want to start text. I want to pick this font. I want to move the cursor here and then I'm going to paint the text, you know from that cursor position. So HTML will automatically wrap stuff, but if we want to do that, we have to split up the lines manually. So here's an example where we did that we measure the Box bounding box. And then you measure each character and then you figure out how many words will fit into that and you break it in the lines and then use the paint all the lines yourself. So this is a challenge for us. There are some libraries you can put on top of the libraries, but it was a challenge.&lt;/p&gt;

&lt;p&gt;The second thing I want to talk about is fonts. It seems like it should be really simple but with PDFs they're not, and I think one of the reasons is because PDFs are supposed to be self-contained and they weren't really built to be edited. So there's no fallback fonts, the font glyphs like the specific characters to render that in that font is embedded inside of the PDF. You can't mix fonts in the text block. You can't say like here's a sentence bold. This word. You have to draw those things independently. So it is HTML going back to that. This is a lot simpler. Right? Like you just specify a whole bunch of fonts and the browser will figure it out. And if we use a character that our font doesn't support the browser will find a font. That does have that character, and display it. So it's Magic!&lt;/p&gt;

&lt;p&gt;Fonts in a PDF are a little bit more complicated. So there's a lot going on in this slide. First of all I picked this on Google called Noto and it doesn't have the “shi” character. So I can't use it. It won't render in the PDF. The second thing is that I'm drawing the regular and the Bold word independently, and on the right there, we're embedding all of the glyphs for those specific words. So from the regular we're getting the hclo and then from the bold, we're getting over the world characters. So if we were to say use a lowercase H, then it wouldn't display because it's PDF doesn't have that font. And so if you're generating a new PDF, you have to be really careful about what characters come in because it has to be in the font. So that was a challenge. The next thing is rotations. Look at this you like that seems fun right seems fine. But then you go to print it and it's rotated.Okay cool. So this happens a lot with scanned documents. They'll scan them upside down and then something in this camera software will set a rotation on the pages to be 180. So it looks to the user like it's it's not rotated but under the hood it is.&lt;/p&gt;

&lt;p&gt;And so this is hard because if you want to read the coordinates of something or you want to add something back into the PDF, you need to sort of back out all of these rotations and figure out like what exactly is happening so that you can orient your thing that way, so it looks right to user. So like you have to know if there's partitions of the page there's this thing called a Content stream. It can be rotated, there could be rotations on form fields. So this is a challenge to serve work out like okay, we're going to add this thing into a Content stream. How's the page? How’s the content stream and we have to sort of orient our coordinate system to manage that. Okay, so that's just a couple of the challenges that we ran into. There's a whole bunch of other things that are hard as well, like dealing with forms can be complicated depending on how you want to interact with them. Signatures are like their own talk. They're crazytown! And then extracting content can also be hard because of the absolute positioning. So the last line on a page could actually be first in the file. So extracting content can be difficult. There’s a whole bunch of other things too that can be hard.&lt;/p&gt;

&lt;p&gt;Alright. So we've established that previous are good for screens and printers, not so great for humans because it's cumbersome and error prone to fill out PDFs, fill out forms. It's also pretty hard for developers to set up and maintain something, even for simple use cases. So like, could we fix this? If a computer fills out a PDF then the filled PDF is just a consumable, it's no different than say an e-book or resume or something like that, just a piece of information that you can reference later. So then it becomes good for humans. So the last piece of this then is trying to make it so that Developers can fill out all these PDFs so that humans don't have to deal with it. And that's what we've been working on. We've been working on, and we released recently, this PDF API, that does e-signatures, PDF filling, and PDF generation with Markdown right now.&lt;/p&gt;

&lt;p&gt;So our goal is to make these things super easy to use.&lt;/p&gt;

&lt;p&gt;Okay, now I'll get into the demo.&lt;/p&gt;

&lt;p&gt;Okay. I hope everyone can see that. So I'm going to take you through the three different things that we do so we have a generated PDF, what we're going to do is going to generate a brand new PDF. Generate this invoice. And so we are able to send a JSON payload to a single endpoint and it will respond with the PDF bytes. So We're going to be using our node client, which is just for authentication helpers and then also some helpers for actually calling the endpoints. I'm going to build up our data here. And then all we're going to do is create the client and send, and then generate the PDF with our data. So each one of these is like each of these objects is a section on the PDF, and they’re separated by a little bit of padding here. They support markdown and then I have some helpers for creating tables, because  it's a common use case. So this thing's already generated. So I'll show you a little bit. We'll add some more stuff here. We'll add will bold. If some we will have blood this thing. Let's make this left aligned, alright sweet. So, then we generate it and then magically works and so we just automatically get markdown support. Here and table support and all that. So that's it for generating APIs.&lt;/p&gt;

&lt;p&gt;Okay, so moving on to the filling APIs. It's similar to the generate API except that we have a template. So we have an existing template that we have already created, uploaded into our system, and the boxes were found when we uploaded it. And so each one of these boxes has an ID on it, so we are able to send data to each one of these IDs  and it will fill out the template. So we are going to generate this W4. Okay, so this thing works really similar to the... first I’ll generate it, how about that.&lt;/p&gt;

&lt;p&gt;So we got it. We filled it out. Awesome. It works really similar to the generation endpoint where we just have a payload of data and then we create the client and we send the payload with the template ID to the system. And in response with the PDF bytes, we save it to disk and that's it. So it's pretty simple to add new things. So we'll go over here and we'll add a box here, and we’ll call this “things”. Now that we've done that we have a new “things”, it supports this new box, so we can go in here and this anywhere, we generated it. Okay, so then it worked.&lt;/p&gt;

&lt;p&gt;Cool. So that's the The Filling piece.&lt;/p&gt;

&lt;p&gt;And so the last thing that I want to talk about is the signature packet. So this is a little bit more complicated than the last two because there's just a lot more going on. So this example what we're going to do is we're going to create a packet for onboarding a new employee. So  an employee needs to fill out this W4 and then we're going to have an NDA generated for them. And then we're going to have them sign, employees going to sign both documents in the employer is going to sign one document. So two PDFs, two signers. First thing we do is we set up the email addresses. Now for this purpose of this example, and simplicity, Anvil is going to handle the signing process, it will send out emails to all the signers when it's their turn to side, but that's totally configurable. So you can set up a packet where everything is handled in your application, where it's embedded in your application. We don’t send you our users any emails. Okay. So we're going to use the email setup, so we set up the emails so that the names this works semi-similar to the other ones, in that it's just one endpoint. This is a GraphQL endpoint. We're going to call color mutation with a bunch of variables to set up a packet, and it's going to respond with some packet information. So in the variables, we set up the files, we want to use our PDF or W4. We want to upload a new NDA, and we're going to draw the boxes, and then we specify the data that we want to go onto each one of these.&lt;/p&gt;

&lt;p&gt;These files so that's the w4 one. Here's the NDA. And then we set up the signers as well. So it's just his like which files who signs which files. Okay cool. So we are going to go to this other terminal first and then we create the Etch packet.&lt;/p&gt;

&lt;p&gt;Alright, so we get this little URL here that it's the details URL. So this is a URL for the details of our new packet. So you can go on your dashboard. You can see the status of the packet, and see who's signed and who hasn't and all those things. Also I should have an email to sign these documents. So Sally is the employee, we are going through, and I'm going to sign.&lt;/p&gt;

&lt;p&gt;Accept my signature, start signing, so you can see that all of this data has been filled. So the W4 has been filled with the data that we specified, and the NDA has been filled with the data that we specified. So all we need to do is then sign this.&lt;/p&gt;

&lt;p&gt;And it's waiting then for the employer. So get a new email. So this is the employer side of things. And then we can see all of the signatures, we can see Sally's signature here, and then we can see Sally’s signature there. We sign it and it's all done, so with that, everyone gets a “completed” email. and also we can see it on the dashboard. Okay, great. That’s all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;To sign up for our free developer sandbox or learn more about our API, head over to our developer center at &lt;a href="https://dev.to/developers"&gt;www.useanvil.com/developers&lt;/a&gt;. There, you will find comprehensive documentation, simple tutorials, and client libraries to help you get started quickly and easily.&lt;/p&gt;

&lt;p&gt;If you have questions, please do not hesitate to contact us at:&lt;br&gt;
&lt;a href="//mailto:developers@useanvil.com"&gt;developers@useanvil.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>pdf</category>
      <category>webdev</category>
      <category>java</category>
      <category>javascript</category>
    </item>
    <item>
      <title>New PDF Generation Features: customizable font size, color, table gridlines, and alignment</title>
      <dc:creator>Anvil Engineering</dc:creator>
      <pubDate>Mon, 26 Apr 2021 04:40:03 +0000</pubDate>
      <link>https://dev.to/anvilfoundry/new-pdf-generation-features-customizable-font-size-color-table-gridlines-and-alignment-4e4o</link>
      <guid>https://dev.to/anvilfoundry/new-pdf-generation-features-customizable-font-size-color-table-gridlines-and-alignment-4e4o</guid>
      <description>&lt;p&gt;For this blog post, we’ll assume you’re already familiar with Anvil’s API. In case you’re not, I recommend taking 5 minutes to read this &lt;a href="https://www.useanvil.com/blog/2021-02-05-generate-invoice-pdf"&gt;tutorial on generating an invoice PDF&lt;/a&gt; to quickly get up to speed.&lt;/p&gt;

&lt;p&gt;There’s tools to simplify almost all daily tasks, but when it comes to PDFs, it’s quite lacking. Anvil is on its way to changing that, and today I’m excited to announce a number of new features to further streamline PDF generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;With Anvil’s PDF generation API, the input is accepted in JSON format with the content as an array of objects defined under the &lt;code&gt;data&lt;/code&gt; key.&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="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Anvil Doghouse Project Invoice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fontSize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;textColor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#171717&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&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="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;Let’s first take a look at the new features.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Per field customizable font size and color&lt;/li&gt;
&lt;li&gt;Option to show or hide table row and column gridlines&lt;/li&gt;
&lt;li&gt;Adjustable table column widths&lt;/li&gt;
&lt;li&gt;Vertical alignment of table content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ll go through with a brief description of each feature then demonstrate them with an example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 1. Adjusting font size and color on each field
&lt;/h2&gt;

&lt;p&gt;Each object under the &lt;code&gt;data&lt;/code&gt; array is a field, and with the new release you’re able to adjust the font size and color of each field. Define &lt;code&gt;fontSize&lt;/code&gt; and &lt;code&gt;textColor&lt;/code&gt; to your liking on each field object, and you’re good to go.&lt;/p&gt;

&lt;p&gt;If not specified, the font size and color will be inherited from the PDF body.&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;March 4th, 2024&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fontSize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;textColor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#616161&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="s2"&gt;label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;To&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Phoebe Beckett - phoebe@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fontSize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;textColor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#006ec2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qGoNaAAE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2h24coh4f7hix5a3ptgl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qGoNaAAE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2h24coh4f7hix5a3ptgl.png" alt="Customizing font size and color on fields"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 2. Displaying table row and column gridlines
&lt;/h2&gt;

&lt;p&gt;In the case you want your table content to be more structured and defined, the &lt;code&gt;rowGridlines&lt;/code&gt; and &lt;code&gt;columnGridlines&lt;/code&gt; booleans are your friend. To add gridlines in-between each table row, set &lt;code&gt;rowGridlines&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;. Likewise for &lt;code&gt;columnGridlines&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&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="s2"&gt;label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Building material expenses&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;table&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="s2"&gt;rows&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Quantity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Price&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="s2"&gt;3x Roof Shingles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;15&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$60.00&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="s2"&gt;5x Hardwood Plywood&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$300.00&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="s2"&gt;80x Wood Screws&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;80&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$45.00&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="s2"&gt;rowGridlines&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;columnGridlines&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bYhNoLLm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4984upb3gghh2ef07iq3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bYhNoLLm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4984upb3gghh2ef07iq3.png" alt="Table with row and column gridlines"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 3. Adjusting table column width down to the pixel
&lt;/h2&gt;

&lt;p&gt;This is another feature for tables, giving you more control of how your tables are formatted. Under &lt;code&gt;columnOptions&lt;/code&gt;, specify your &lt;code&gt;width&lt;/code&gt; in either pixels or percentage.&lt;/p&gt;

&lt;p&gt;For the example below, column 1 is defined to have a width &lt;code&gt;60%&lt;/code&gt; of the table’s total width and column 3 would have a width of &lt;code&gt;200px&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&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="s2"&gt;table&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="s2"&gt;rows&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="s2"&gt;columnOptions&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;align&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;width&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;40%&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="s2"&gt;align&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;left&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="s2"&gt;align&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;width&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;200px&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="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oUNXp0VI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2k96phop1a4lt6eczw5k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oUNXp0VI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2k96phop1a4lt6eczw5k.png" alt="Table with column gridlines"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 4. Vertically aligning table content
&lt;/h2&gt;

&lt;p&gt;Horizontally aligning content is already supported within our tables, and now you can add vertical alignment! Set &lt;code&gt;verticalAlign&lt;/code&gt; to &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;center&lt;/code&gt;, or &lt;code&gt;bottom&lt;/code&gt; to adjust the vertical position of your table content.&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&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="s2"&gt;table&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="s2"&gt;rows&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="s2"&gt;columnOptions&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="s2"&gt;verticalAlign&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;center&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="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OJgrcPYd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rhag609hne4te47omwws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OJgrcPYd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rhag609hne4te47omwws.png" alt="Table with content center vertically aligned"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summing It Up
&lt;/h2&gt;

&lt;p&gt;What better way to summarize all our new features than with an example? Here is our PDF generation API endpoint, improved with more customizability.&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="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Anvil Doghouse Project Invoice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fontSize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;textColor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#171717&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;March 4th, 2024&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fontSize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;textColor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#616161&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="s2"&gt;label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;To&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Phoebe Beckett - phoebe@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fontSize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&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="s2"&gt;label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;From&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Anvil Inc. - hello@useanvil.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fontSize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&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="s2"&gt;label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Building material expenses&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;table&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="s2"&gt;rows&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Quantity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Price&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="s2"&gt;3x Roof Shingles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;15&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$60.00&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="s2"&gt;5x Hardwood Plywood&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$300.00&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="s2"&gt;80x Wood Screws&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;80&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$45.00&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;columnOptions&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;align&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;width&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;60%&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="s2"&gt;align&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;width&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100px&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="s2"&gt;align&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;right&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="s2"&gt;verticalAlign&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;top&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;firstRowHeaders&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rowGridlines&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;columnGridlines&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fontSize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&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="s2"&gt;label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Food expenses&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;table&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="s2"&gt;rows&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Quantity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Price&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="s2"&gt;20x Dog Biscuits&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;20&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$50.00&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;columnOptions&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;align&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;width&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;40%&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="s2"&gt;align&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;left&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="s2"&gt;align&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;width&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;200px&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="s2"&gt;verticalAlign&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;firstRowHeaders&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rowGridlines&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;columnGridlines&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fontSize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&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="s2"&gt;label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Total Amount&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;table&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="s2"&gt;rows&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;**Tax**&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$64.80&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="s2"&gt;***Total***&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$784.80&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="s2"&gt;firstRowHeaders&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fontSize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;textColor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#db0000&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tL6i9vws--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ty6w2wj1mzw4ibvhczq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tL6i9vws--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ty6w2wj1mzw4ibvhczq.png" alt="The complete invoice"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you’re all prepped up to build your own PDF programmatically from JSON input. PDFs can be tricky to deal with in all sorts of situations, but with Anvil we’ll take care of that so you can focus on solving problems more important to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Take a look at our &lt;a href="https://www.useanvil.com/docs/api/generate-pdf"&gt;PDF generation API docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Experiment with our API using the &lt;a href="https://documenter.getpostman.com/view/14149649/TVzSjcj6"&gt;Anvil API Postman collection&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Questions or feedback? Contact us at &lt;a href="mailto:developers@useanvil.com"&gt;developers@useanvil.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>api</category>
      <category>tutorial</category>
      <category>pdf</category>
      <category>webdev</category>
    </item>
    <item>
      <title>PDF API vs Workflow API</title>
      <dc:creator>Anvil Engineering</dc:creator>
      <pubDate>Mon, 26 Apr 2021 04:39:13 +0000</pubDate>
      <link>https://dev.to/anvilfoundry/pdf-api-vs-workflow-api-1b9i</link>
      <guid>https://dev.to/anvilfoundry/pdf-api-vs-workflow-api-1b9i</guid>
      <description>&lt;h1&gt;
  
  
  What's the difference?
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Anvil PDF API
&lt;/h3&gt;

&lt;p&gt;The Anvil PDF API consists of three endpoints that simplify integrating PDFs into your application. These three endpoints are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PDF generation&lt;/strong&gt; - Generate brand new PDFs such as invoices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDF filling&lt;/strong&gt; - Fill in any existing PDF template such as IRS form W-4.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Etch e-Sign&lt;/strong&gt; - Request signatures on PDFs from multiple parties.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a single API request, you can prepare a signature packet by uploading a custom PDF and/or filling in an existing PDF template, and then request signatures from all signing parties.&lt;/p&gt;

&lt;p&gt;It is called the PDF API because these endpoints operate directly at the PDF level.&lt;/p&gt;

&lt;p&gt;For both the filling and generation API endpoints, there is no visible Anvil UI that is displayed in your application. Your application simply makes a web request to Anvil and Anvil responds with PDF byte code.&lt;/p&gt;

&lt;p&gt;For the E-signature API endpoint, Anvil provides a signing UI that can be embedded in your application, presented in a modal, or redirected to a standalone page from your application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anvil Workflows
&lt;/h3&gt;

&lt;p&gt;Anvil Workflows combines PDF filling, generating, and e-sign capabilities with an intuitive, web-based UI to collect information. The workflow builder allows anyone to quickly create an online experience that replaces offline paperwork processes or the need to manually develop online forms. The Workflow API allows you to programmatically interface with these workflows with seamless integration into your application. There are two flavors of Workflow APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Workflow GraphQL&lt;/strong&gt; - This is a powerful suite of endpoints for interacting with Anvil. Almost all Anvil interactions are available in the GraphQL endpoints. Start new submissions, track submission progress, lock and unlock submissions, void signature packets etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workflow URL&lt;/strong&gt; - This is a simple interface for creating new submissions to a workflow and pre-populating the workflow with data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Depending on the level of integration and the complexity of your application, you can choose between the flexibility of the GraphQL endpoints and the ease of the Workflow URL endpoints.&lt;/p&gt;

&lt;h3&gt;
  
  
  Webhooks
&lt;/h3&gt;

&lt;p&gt;Webhooks round out the Anvil developer platform. Webhooks can be registered for Anvil Workflows as well as the Etch e-Sign endpoint. The PDF filling and PDF generation endpoints are synchronous API endpoints, so webhooks do not apply for these endpoints.&lt;/p&gt;

&lt;p&gt;Anvil webhooks allow you to be notified of certain events within the Anvil system. These include new submissions created, submissions complete, signings completed, and more. To get a full list of webhook events, take a look at the Anvil webhook documentation.&lt;/p&gt;

&lt;h1&gt;
  
  
  Which solution is right for me?
&lt;/h1&gt;

&lt;p&gt;In selecting the best way to integrate Anvil via API, there are a couple of things to consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application complexity&lt;/li&gt;
&lt;li&gt;UI and UX ownership&lt;/li&gt;
&lt;li&gt;Allocation of development resources&lt;/li&gt;
&lt;li&gt;Pricing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Application complexity
&lt;/h3&gt;

&lt;p&gt;Is the workflow you are automating a core value proposition of your product? What is the “secret sauce” of what you are trying to build, and where would you like that “secret sauce” to live?&lt;/p&gt;

&lt;p&gt;Using Anvil Workflows can greatly accelerate your product development. If the business logic is simple or there is minimal proprietary code in your application, then using Anvil workflows can offload a lot of effort from your developers, allowing them to refocus their energy on building and improving core features. Using Anvil will help you build faster, and get to market sooner.&lt;/p&gt;

&lt;p&gt;Alternatively, if there are proprietary algorithms, industry-specific integrations, or changing business requirements with various SLAs, then owning the workflow development cycle might be important to you. In this case, you can rely on Anvil as the PDF layer that integrates seamlessly into your existing development process.&lt;/p&gt;

&lt;p&gt;In summary, if you require a high degree of control of the software, use the PDF API. But if you are pulling together multiple tools and the business logic is simple, Anvil Workflows are a great option.&lt;/p&gt;

&lt;h3&gt;
  
  
  UI and UX ownership
&lt;/h3&gt;

&lt;p&gt;Is your company mainly innovating around creating a 10x better user experience? Is having a cohesive look and feel across all your customer touchpoints important?&lt;/p&gt;

&lt;p&gt;When it comes to UI and UX, Anvil Workflows provide standardized UI components with pre-built validation. You can add custom validation but you cannot change the look and feel of each individual component. The goal is to provide a comprehensive foundation for building online forms, trading fidelity for speed. Think of the workflow platform as a higher-order software library that abstracts away the nitty-gritty of CSS styling, field validation, and component-level integrations such as Google Location API for address fields.&lt;/p&gt;

&lt;p&gt;Alternatively, the Anvil PDF API sits in the background and operates via server-side web interactions. Other than the Etch e-sign view, there is no Anvil UI that is displayed, giving you complete control of your application’s look and feel.&lt;/p&gt;

&lt;p&gt;Anvil Workflows can mean significantly less work for your development team at the expense of controlling the look and feel of your application. On the other hand, the Anvil PDF API is extremely simple to integrate but will require more work from your front-end developer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Allocation of development resources
&lt;/h3&gt;

&lt;p&gt;What is the best use of your time and effort? What area of expertise are your team’s skills best suited to?&lt;/p&gt;

&lt;p&gt;When building an application, the last thing you want to worry about is wrestling with dependencies, deploying a new library, or fixing CSS styling. Hiring and recruiting teammates can be time-consuming and costly.&lt;/p&gt;

&lt;p&gt;Anvil Workflows are easy to create, even for non-developers. You can bring on a team of operational specialists, people who understand the process, and empower them with the technical tools to build their own workflows.&lt;/p&gt;

&lt;p&gt;The PDF API is unobtrusive, operates in the background, allowing your application to take center stage. The integration is simple but does require a deeper level of software development expertise, beyond the level of knowledge expected of non-engineers.&lt;/p&gt;

&lt;p&gt;If you are technical, or you have a large developer team, then the PDF API is a viable option. If you are non-technical or are constrained in terms of developer resources, use the Anvil Workflow platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pricing
&lt;/h3&gt;

&lt;p&gt;How much do you want to spend on third-party workflow tools?&lt;/p&gt;

&lt;p&gt;Here’s how the PDF API pricing breaks down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PDF filling or generation:&lt;/strong&gt; Starts at $0.10 / completion&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Etch E-Sign:&lt;/strong&gt; Starts at $1.50 / completed packet  (Can have multiple signers, includes filling or generation)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And here’s pricing for the Workflow platform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Workflow:&lt;/strong&gt; Starts at $1 / completed submission&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workflow w/ Etch e-Sign:&lt;/strong&gt; Starts at $2.50 / completed submission&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workflow API endpoints:&lt;/strong&gt; Free, unlimited requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Inquire about bulk discounts for high-volume enterprises by emailing &lt;a href="//mailto:hello@useanvil.com"&gt;hello@useanvil.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Although on the surface Anvil Workflows are more expensive, they can actually be more cost effective in the long-run.&lt;br&gt;
Workflows are not billed per API request, only when a submission to the workflow is completed.&lt;br&gt;
The cost of developing an application from scratch can be much higher. With the Anvil platform, you can build the same application faster and with fewer developer resources.&lt;/p&gt;

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

&lt;p&gt;The general rule of thumb: if you are adding PDFs as part of an existing application, or if you would like to retain posession over the business logic, UI or UX, then the PDF API is a good choice. The Anvil Workflow platform is a great option if you are looking for a fast way to deploy new workflows, want a consistent way to gather and interact with customer data, or are constrained on developer resources.&lt;/p&gt;

&lt;p&gt;Even if you think the PDF APIs are the right choice for you today, we encourage you to explore our Workflow platform. Empowering non-developers on your team to build, deploy and manage their own workflows is an incredibly powerful paradigm shift in how businesses of the future will operate.&lt;/p&gt;

&lt;p&gt;Whatever decision you make, Anvil’s goal is to provide a simple yet flexible platform to help you streamline paperwork and data-gathering for your business.&lt;br&gt;
Resources&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Anvil Developer Page - &lt;a href="https://www.useanvil.com/developers"&gt;www.useanvil.com/developers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Anvil API documentation - &lt;a href="https://www.useanvil.com/docs"&gt;www.useanvil.com/docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have questions, please do not hesitate to contact us at &lt;a href="//mailto:developers@useanvil.com"&gt;developers@useanvil.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>workflow</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Introducing the complete PDF API</title>
      <dc:creator>Anvil Engineering</dc:creator>
      <pubDate>Mon, 26 Apr 2021 00:44:32 +0000</pubDate>
      <link>https://dev.to/anvilfoundry/introducing-the-complete-pdf-api-1l7</link>
      <guid>https://dev.to/anvilfoundry/introducing-the-complete-pdf-api-1l7</guid>
      <description>&lt;p&gt;Anvil was started with a simple premise: paperwork and PDFs are the primary bottlenecks restricting faster adoption of digital tools in every industry. We set out to build software that bridges the past and all of its entrenched paper processes to the future of online-first, data-centric operations. Using Anvil, companies can free themselves from the burden of endless paperwork, menial tasks, and antiquated ways of working, while maintaining all that is good about long-established processes: maintaining order, ensuring data integrity, and guarding against exceptions.&lt;/p&gt;

&lt;p&gt;On the surface, the solution appears simple. First, digitally capture some data, then fill out the relevant PDF form, and finally pass the completed form to another party. In reality, paperwork processes are littered with nuances, exceptions, and business rules, each adding to the capabilities and complexity of our product.&lt;/p&gt;

&lt;p&gt;While Anvil’s &lt;a href="https://www.useanvil.com/workflows"&gt;workflow builder&lt;/a&gt; is a great solution for solving a large category of paper workflow challenges, some complex processes warrant a software solution built by developers with deep industry-specific knowledge. So how can Anvil play a role in helping these developers? By making it extremely easy to work with the PDFs that underpin these processes.&lt;/p&gt;

&lt;h3&gt;
  
  
  How did PDFs become ubiquitous?
&lt;/h3&gt;

&lt;p&gt;Let’s start with an abbreviated tour of the technology that enables information sharing. Throughout history, humans have devised increasingly effective ways of sharing information farther and faster.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--brSlrlB4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xm07rfvobhcygds0r86b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--brSlrlB4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xm07rfvobhcygds0r86b.png" alt="Communication methods"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;&lt;center&gt;speech → writing → printing press → typewriter → computer&lt;/center&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These technologies allowed for information to be created and shared faster, but the underlying medium that contained the information remained largely the same, paper.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6aS4cBrP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sf5tsl6dl37df116yfw2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6aS4cBrP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sf5tsl6dl37df116yfw2.png" alt="Communication Medium"&gt;&lt;/a&gt;&lt;strong&gt;&lt;center&gt;Paper&lt;/center&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With the emergence of computers, information could be created faster but still needed to be printed for it to be shared farther.&lt;/p&gt;

&lt;p&gt;Enter PDFs, a coordinate-based file format created to imitate paper and describe documents so that they can easily be printed. With paper being the dominant medium for so long, it made sense for PDFs to adopt the same look and feel on a screen. But the key to the PDF’s success is the combination of:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The consistency of the PDF file regardless of the program used to view it. Just like a printed paper document maintains its form regardless of who views it, the PDF looks consistent every time it is displayed. Other file formats varied in appearance depending on the program used to open the file.&lt;/li&gt;
&lt;li&gt;The small file size of PDFs, allowing it to be transferred easily over the internet. Remember, internet speeds were very slow in the early days, so file size mattered a lot.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With these two advantages, the PDF became a foundational transitional technology - it bridged the gap between the familiar (paper) with the new (digital).&lt;/p&gt;

&lt;p&gt;But as the internet evolved, the consistent presentation of information diminished in importance. It is the underlying data that is important, not that the information is presented on a digital sheet of paper.&lt;/p&gt;

&lt;p&gt;This could have been the moment to phase out PDF technology and usher in a new, modern model for information sharing. For some industries, the transition to all-digital, data-first was easy. For others, especially legacy industries hamstrung by regulatory requirements, this transition has been painful, slow, and tedious. We all agree that transacting in data over the internet is the future, but how do we get there? How do we accommodate the past while orienting ourselves towards the future?&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing the Anvil PDF API
&lt;/h2&gt;

&lt;p&gt;Returning to our original insight:&lt;/p&gt;

&lt;p&gt;Paperwork and PDFs are the primary bottlenecks restricting faster adoption of digital tools in every industry.&lt;/p&gt;

&lt;p&gt;In building &lt;a href="https://www.useanvil.com/workflows/"&gt;Anvil Workflow&lt;/a&gt;, we have developed a deep knowledge of PDFs: how to create them, manipulate them, and electronically sign them. We also have an extensive library of tools that make interacting with PDFs online easy. Combined, these tools can accelerate companies already building vertical-specific solutions by offloading the complexities of handling PDFs to Anvil.&lt;/p&gt;

&lt;p&gt;Today we are announcing the complete PDF API to help companies focus on building specialized solutions for their respective industries without having to worry about the underlying paperwork. These API endpoints include:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.useanvil.com/docs/api/fill-pdf"&gt;PDF Filling&lt;/a&gt;&lt;/strong&gt; - A simple solution to fill in existing PDF documents. Set up a template in minutes, then make a request to the unique template URL with data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.useanvil.com/docs/api/generate-pdf"&gt;PDF Generation&lt;/a&gt;&lt;/strong&gt; - An endpoint for creating PDFs from scratch. This endpoint supports markdown formatting and dynamic length tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.useanvil.com/docs/api/e-signatures"&gt;Etch PDF E-Sign&lt;/a&gt;&lt;/strong&gt; - An easy, flexible, embeddable and customizable e-signature solution that just works. You can also fill out a PDF form and/or generate a new PDF to be included in the signature packet with one API call.&lt;/p&gt;

&lt;p&gt;These three API endpoints are designed to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Address the broadest paperwork challenges that any technology company entering a legacy industry will encounter.&lt;/li&gt;
&lt;li&gt;Sit in the background so that your product can be front and center with your customers, allowing you to own the critical business logic that is the secret sauce of your technology solution.&lt;/li&gt;
&lt;li&gt;Address technical headaches of managing PDFs. Most PDF manipulation libraries are written in Java/C# etc., require self-hosting, and still need to run headless Chrome to do any PDF rendering.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why use the Anvil PDF API?
&lt;/h3&gt;

&lt;p&gt;In designing these API endpoints, we had three main goals:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity&lt;/strong&gt; - The API can be integrated into your solution in minutes. We imagined how developers would consume our API and then designed the API to best match those use cases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliability and scalability&lt;/strong&gt; - We have tested our API under heavy, real-world load. From helping underwrite millions in PPP loans, to onboarding thousands of migrant farmworkers, Anvil is ready to scale as you scale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer experience&lt;/strong&gt; - We are developers, and we want our developer experience to be an enjoyable one. To help you get started with Anvil, we have released a number of useful tools including:
a. Comprehensive and well organized documentation
b. An interactive PDF e-sign tutorial
c. Postman API collection
d. Open source node.js client for interacting with our API
e. UI tools to make templating PDFs easier and faster&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Have a suggestion or idea for improving the developer experience? Let us know at &lt;a href="//mailto:developers@useanvil.com"&gt;developers@useanvil.com&lt;/a&gt;, we are always looking for insight from our community.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it’s going
&lt;/h2&gt;

&lt;p&gt;We are excited by the early use cases we have seen with Anvil. Companies across many industries have used our system to programmatically create documents for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HR&lt;/li&gt;
&lt;li&gt;Insurance&lt;/li&gt;
&lt;li&gt;Financial services and banking&lt;/li&gt;
&lt;li&gt;Legal&lt;/li&gt;
&lt;li&gt;Education&lt;/li&gt;
&lt;li&gt;Healthcare&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are all industries that have entrenched paperwork processes, each of which requires deep knowledge to understand. There are many talented teams building innovative vertical-specific solutions. Anvil’s complete PDF API can help them focus on the core solution instead of wrestling with PDFs. By providing the digital, web-first building blocks for PDFs, Anvil is building the technology that helps accelerate the change from data stuck on paper and PDFs to a modern  interconnected world with free data exchange.&lt;/p&gt;

&lt;p&gt;To sign up for our free developer sandbox or learn more about our API, head over to our developer center at &lt;a href="https://www.useanvil.com/developers"&gt;www.useanvil.com/developers&lt;/a&gt;. There, you will find comprehensive documentation, simple tutorials, and client libraries to help you get started quickly and easily.&lt;/p&gt;

&lt;p&gt;If you have questions, please do not hesitate to contact us at:&lt;br&gt;
&lt;a href="//mailto:developers@useanvil.com"&gt;developers@useanvil.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Implementing 429 retries and throttling for API rate-limits</title>
      <dc:creator>Mang-Git Ng</dc:creator>
      <pubDate>Tue, 30 Mar 2021 17:20:10 +0000</pubDate>
      <link>https://dev.to/anvilfoundry/implementing-429-retries-and-throttling-for-api-rate-limits-1nne</link>
      <guid>https://dev.to/anvilfoundry/implementing-429-retries-and-throttling-for-api-rate-limits-1nne</guid>
      <description>&lt;p&gt;&lt;em&gt;Learn how to handle 429 Too Many Requests responses when consuming 3rd party APIs.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most APIs in the wild implement rate-limits. They say "you can only make X number of requests in Y seconds". If you exceed the specified rate-limits, their servers will reject your requests for a period of time, basically saying, "sorry we didn't process your request, please try again in 10 seconds."&lt;/p&gt;

&lt;p&gt;Many language-specific SDKs and clients, even from major API providers, don't come with built-in rate-limit handling. For example, Dropbox's &lt;a href="https://github.com/dropbox/dropbox-sdk-js"&gt;node client&lt;/a&gt; does not implement throttling.&lt;/p&gt;

&lt;p&gt;Some companies provide an external module like GitHub's &lt;a href="https://github.com/octokit/plugin-throttling.js"&gt;plugin-throttling package&lt;/a&gt; for their &lt;a href="https://github.com/octokit/graphql.js"&gt;node&lt;/a&gt; &lt;a href="https://github.com/octokit/rest.js/"&gt;clients&lt;/a&gt;. But often it's up to you to implement.&lt;/p&gt;

&lt;p&gt;These rate-limits can be annoying to deal with, especially if you're working with a restrictive sandbox and trying to get something up and running quickly.&lt;/p&gt;

&lt;p&gt;Handling these in an efficient manner is more complex than it seems. This post will walk through a number of different implementations and the pros and cons of each. We'll finish with an &lt;a href="https://gist.github.com/benogle/723e60aa020f85ad6adc8e7beb70e705"&gt;example script&lt;/a&gt; you can use to run benchmarks against the API of your choice. All examples will be in vanilla JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick and dirty ⏱️
&lt;/h2&gt;

&lt;p&gt;Maybe you just want to get something working quickly without error. The easiest way around a rate-limit is to delay requests so they fit within the specified window.&lt;/p&gt;

&lt;p&gt;For example if an API allowed 6 requests over 3 seconds, the API will allow a request every 500ms and not fail (&lt;code&gt;3000 / 6 = 500&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for&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;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;callTheAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// HACK!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;sleep&lt;/code&gt; is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;milliseconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;milliseconds&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;This is poor practice!&lt;/em&gt; It still could error if you are on the edge of the time window, and it can't handle legitimate bursts. What if you only need to make 6 requests? The code above will take 3 seconds, but the API allows doing all 6 in parallel, which will be significantly faster.&lt;/p&gt;

&lt;p&gt;The sleep approach is fine for hobby projects, quick scripts, etc—I admit I've used it in local script situations. But you probably want to keep it out of your production code.&lt;/p&gt;

&lt;p&gt;There are better ways!&lt;/p&gt;

&lt;h2&gt;
  
  
  The dream
&lt;/h2&gt;

&lt;p&gt;The ideal solution hides the details of the API's limits from the developer. I don't want to think about how many requests I can make, just make all the requests efficiently and tell me the results.&lt;/p&gt;

&lt;p&gt;My ideal in JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&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="nx"&gt;callTheAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&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;As an API consumer, I also want all my requests to finish as fast as they can within the bounds of the rate-limits.&lt;/p&gt;

&lt;p&gt;Assuming &lt;strong&gt;10&lt;/strong&gt; requests at the previous example limits of &lt;strong&gt;6&lt;/strong&gt; requests over &lt;strong&gt;3&lt;/strong&gt; seconds, what is the theoretical limit? Let's also assume the API can make all 6 requests in parallel, and a single request takes &lt;strong&gt;200ms&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first 6 requests should complete in 200ms, but need to take 3 seconds because of the API's rate-limit&lt;/li&gt;
&lt;li&gt;The last 4 requests should start at the 3 second mark, and only take 200ms&lt;/li&gt;
&lt;li&gt;Theoretical Total: 3200ms or 3.2 seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ok, let's see how close we can get.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling the error response
&lt;/h2&gt;

&lt;p&gt;The first thing we need to nail down is how to handle the error responses when the API limits are exceeded.&lt;/p&gt;

&lt;p&gt;If you exceed an API provider's rate-limit, their server should respond with a &lt;code&gt;429&lt;/code&gt; status code  (&lt;code&gt;Too Many Requests&lt;/code&gt;) and a &lt;code&gt;Retry-After&lt;/code&gt; header.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;429
Retry-After: 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Retry-After&lt;/code&gt; header may be either in &lt;strong&gt;seconds&lt;/strong&gt; to wait or a &lt;strong&gt;date&lt;/strong&gt; when the rate-limit is lifted.&lt;/p&gt;

&lt;p&gt;The header's &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Date"&gt;date format&lt;/a&gt; is not an &lt;a href="https://en.wikipedia.org/wiki/ISO_8601"&gt;ISO 8601&lt;/a&gt; date, but an 'HTTP date' format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;day-name&amp;gt;, &amp;lt;day&amp;gt; &amp;lt;month&amp;gt; &amp;lt;year&amp;gt; &amp;lt;hour&amp;gt;:&amp;lt;minute&amp;gt;:&amp;lt;second&amp;gt; GMT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mon, 29 Mar 2021 04:58:00 GMT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fortunately if you are a JavaScript / Node user, this format is parsable by passing it to the &lt;code&gt;Date&lt;/code&gt; constructor.&lt;/p&gt;

&lt;p&gt;Here's a function that parses both formats in JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getMillisToSleep&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;retryHeaderString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;millisToSleep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;retryHeaderString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;millisToSleep&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;millisToSleep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;retryHeaderString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&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;millisToSleep&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;getMillisToSleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 4000&lt;/span&gt;
&lt;span class="nx"&gt;getMillisToSleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mon, 29 Mar 2021 04:58:00 GMT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 4000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can build out a function that uses the &lt;code&gt;Retry-After&lt;/code&gt; header to retry when we encounter a &lt;code&gt;429&lt;/code&gt; HTTP status code:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;fetchAndRetryIfNecessary&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callAPIFn&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;response&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;callAPIFn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;429&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;retryAfter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;retry-after&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;millisToSleep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getMillisToSleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;retryAfter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;millisToSleep&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;fetchAndRetryIfNecessary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callAPIFn&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;response&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function will continue to retry until it no longer gets a &lt;code&gt;429&lt;/code&gt; status code.&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;// Usage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetchAndRetryIfNecessary&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requestOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're ready to make some requests!&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;I'm working with a local API and running &lt;strong&gt;10&lt;/strong&gt; and &lt;strong&gt;20&lt;/strong&gt; requests with the same example limits from above: &lt;strong&gt;6&lt;/strong&gt; requests over &lt;strong&gt;3&lt;/strong&gt; seconds.&lt;/p&gt;

&lt;p&gt;The best theoretical performance we can expect with these parameters is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10 requests: 3.2 seconds&lt;/li&gt;
&lt;li&gt;20 requests: 9.2 seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's see how close we can get!&lt;/p&gt;

&lt;h2&gt;
  
  
  Baseline: sleep between requests
&lt;/h2&gt;

&lt;p&gt;Remember the "quick and dirty" request method we talked about at the beginning? We'll use its behavior and timing as a baseline to improve on.&lt;/p&gt;

&lt;p&gt;A reminder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;...]&lt;/span&gt;
&lt;span class="k"&gt;for&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;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;callTheAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;6&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;So how does it perform?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With 10 requests: about 7 seconds&lt;/li&gt;
&lt;li&gt;With 20 requests: about 14 seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our theoretical time for serial requests is 5 seconds at 10 requests, and 10 seconds for 20 requests, but there is some overhead for each request, so the real times are a little higher.&lt;/p&gt;

&lt;p&gt;Here's a 10 request pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;⏱️ Running Benchmark Sleep between requests, no retry
Request Start: 0 attempt:0 2021-03-29T00:53:09.629Z
Request End:   0 attempt:0 200 344ms
Request Start: 1 attempt:0 2021-03-29T00:53:10.479Z
Request End:   1 attempt:0 200 252ms
Request Start: 2 attempt:0 2021-03-29T00:53:11.236Z
Request End:   2 attempt:0 200 170ms
Request Start: 3 attempt:0 2021-03-29T00:53:11.910Z
Request End:   3 attempt:0 200 174ms
Request Start: 4 attempt:0 2021-03-29T00:53:12.585Z
Request End:   4 attempt:0 200 189ms
Request Start: 5 attempt:0 2021-03-29T00:53:13.275Z
Request End:   5 attempt:0 200 226ms
Request Start: 6 attempt:0 2021-03-29T00:53:14.005Z
Request End:   6 attempt:0 200 168ms
Request Start: 7 attempt:0 2021-03-29T00:53:14.675Z
Request End:   7 attempt:0 200 195ms
Request Start: 8 attempt:0 2021-03-29T00:53:15.375Z
Request End:   8 attempt:0 200 218ms
Request Start: 9 attempt:0 2021-03-29T00:53:16.096Z
Request End:   9 attempt:0 200 168ms
✅ Total Sleep between requests, no retry: 7136ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Approach 1: serial with no sleep
&lt;/h2&gt;

&lt;p&gt;Now we have a function for handling the error and retrying, let's try removing the sleep call from the baseline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;...]&lt;/span&gt;
&lt;span class="k"&gt;for&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;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetchAndRetryIfNecessary&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;callTheAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&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;Looks like about 4.7 seconds, definitely an improvement, but not quite at the theoretical level of 3.2 seconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;⏱️ Running Benchmark Serial with no limits
Request Start: 0 attempt:0 2021-03-29T00:59:01.118Z
Request End:   0 attempt:0 200 327ms
Request Start: 1 attempt:0 2021-03-29T00:59:01.445Z
Request End:   1 attempt:0 200 189ms
Request Start: 2 attempt:0 2021-03-29T00:59:01.634Z
Request End:   2 attempt:0 200 194ms
Request Start: 3 attempt:0 2021-03-29T00:59:01.828Z
Request End:   3 attempt:0 200 177ms
Request Start: 4 attempt:0 2021-03-29T00:59:02.005Z
Request End:   4 attempt:0 200 179ms
Request Start: 5 attempt:0 2021-03-29T00:59:02.185Z
Request End:   5 attempt:0 200 196ms
Request Start: 6 attempt:0 2021-03-29T00:59:02.381Z
Request End:   6 attempt:0 429 10ms
❗ Retrying:   6 attempt:1 at Mon, 29 Mar 2021 00:59:05 GMT &lt;span class="nb"&gt;sleep &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;2609 ms
Request Start: 6 attempt:1 2021-03-29T00:59:05.156Z
Request End:   6 attempt:1 200 167ms
Request Start: 7 attempt:0 2021-03-29T00:59:05.323Z
Request End:   7 attempt:0 200 176ms
Request Start: 8 attempt:0 2021-03-29T00:59:05.499Z
Request End:   8 attempt:0 200 208ms
Request Start: 9 attempt:0 2021-03-29T00:59:05.707Z
Request End:   9 attempt:0 200 157ms
✅ Total Serial with no limits: 4746ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Approach 2: parallel with no throttling
&lt;/h2&gt;

&lt;p&gt;Let’s try burning through all requests in parallel just to see what happens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="nx"&gt;items&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;responses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&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="nx"&gt;fetchAndRetryIfNecessary&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;callTheAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This run took about 4.3 seconds. A slight improvement over the previous serial approach, but the retry is slowing us down. You can see the last 4 requests all had to retry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;⏱️ Running Benchmark Parallel with no limits
Request Start: 0 attempt:0 2021-03-29T00:55:01.463Z
Request Start: 1 attempt:0 2021-03-29T00:55:01.469Z
Request Start: 2 attempt:0 2021-03-29T00:55:01.470Z
Request Start: 3 attempt:0 2021-03-29T00:55:01.471Z
Request Start: 4 attempt:0 2021-03-29T00:55:01.471Z
Request Start: 5 attempt:0 2021-03-29T00:55:01.472Z
Request Start: 6 attempt:0 2021-03-29T00:55:01.472Z
Request Start: 7 attempt:0 2021-03-29T00:55:01.472Z
Request Start: 8 attempt:0 2021-03-29T00:55:01.472Z
Request Start: 9 attempt:0 2021-03-29T00:55:01.473Z
Request End:   5 attempt:0 429 250ms
❗ Retrying:   5 attempt:1 at Mon, 29 Mar 2021 00:55:05 GMT &lt;span class="nb"&gt;sleep &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;3278 ms
Request End:   6 attempt:0 429 261ms
❗ Retrying:   6 attempt:1 at Mon, 29 Mar 2021 00:55:05 GMT &lt;span class="nb"&gt;sleep &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;3267 ms
Request End:   8 attempt:0 429 261ms
❗ Retrying:   8 attempt:1 at Mon, 29 Mar 2021 00:55:05 GMT &lt;span class="nb"&gt;sleep &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;3267 ms
Request End:   2 attempt:0 429 264ms
❗ Retrying:   2 attempt:1 at Mon, 29 Mar 2021 00:55:05 GMT &lt;span class="nb"&gt;sleep &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;3266 ms
Request End:   1 attempt:0 200 512ms
Request End:   3 attempt:0 200 752ms
Request End:   0 attempt:0 200 766ms
Request End:   4 attempt:0 200 884ms
Request End:   7 attempt:0 200 1039ms
Request End:   9 attempt:0 200 1158ms
Request Start: 5 attempt:1 2021-03-29T00:55:05.155Z
Request Start: 6 attempt:1 2021-03-29T00:55:05.156Z
Request Start: 8 attempt:1 2021-03-29T00:55:05.157Z
Request Start: 2 attempt:1 2021-03-29T00:55:05.157Z
Request End:   2 attempt:1 200 233ms
Request End:   6 attempt:1 200 392ms
Request End:   8 attempt:1 200 513ms
Request End:   5 attempt:1 200 637ms
✅ Total Parallel with no limits: 4329ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks pretty reasonable with only 4 retries, but &lt;strong&gt;this approach does not scale&lt;/strong&gt;. Retries in this scenario only get worse when there are more requests. If we had, say, 20 requests, a number of them would need to retry more than once—we'd need 4 separate 3 second windows to complete all 20 requests, so some requests would need to retry &lt;em&gt;at best&lt;/em&gt; 3 times.&lt;/p&gt;

&lt;p&gt;Additionally, the &lt;a href="https://github.com/tj/node-ratelimiter/blob/f929a523d43f5a393e626d7a685f15b77fab658d/index.js#L67-L73"&gt;ratelimiter implementation&lt;/a&gt; my example server uses will shift the &lt;code&gt;Retry-After&lt;/code&gt; timestamp on subsequent requests when a client is already at the limit—it returns a &lt;code&gt;Retry-After&lt;/code&gt; timestamp based on the 6th oldest request timestamp + 3 seconds.&lt;/p&gt;

&lt;p&gt;That means if you make more requests when you’re already at the limit, it drops old timestamps and shifts the &lt;code&gt;Retry-After&lt;/code&gt; timestamp later. As a result, the &lt;code&gt;Retry-After&lt;/code&gt; timestamps for some requests waiting to retry become stale. They retry but fail because their timestamps were stale. The failure triggers &lt;em&gt;yet another&lt;/em&gt; retry, &lt;em&gt;and&lt;/em&gt; causes the &lt;code&gt;Retry-After&lt;/code&gt; timestamp to be pushed out &lt;em&gt;even further&lt;/em&gt;. All this spirals into a vicious loop of mostly retries. Very bad.&lt;/p&gt;

&lt;p&gt;Here is a shortened log of it attempting to make 20 requests. Some requests needed to retry 35 times (❗) because of the shifting window and stale &lt;code&gt;Retry-After&lt;/code&gt; headers. It eventually finished, but took a whole minute. Bad implementation, do not use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;⏱️ Running Benchmark Parallel with no limits

...many very messy requests...

Request End:   11 attempt:32 200 260ms
Request End:   5 attempt:34 200 367ms
Request End:   6 attempt:34 200 487ms
✅ Total Parallel with no limits: 57964ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Approach 3: parallel with async.mapLimit
&lt;/h2&gt;

&lt;p&gt;It seems like a simple solution to the problem above would be only running &lt;code&gt;n&lt;/code&gt; number of requests in parallel at a time. For example, our demo API allows 6 requests in a time window, so just allow 6 in parallel, right? Let’s try it out.&lt;/p&gt;

&lt;p&gt;There is a node package called &lt;a href="https://www.npmjs.com/package/async"&gt;async&lt;/a&gt; implementing this behavior (among many other things) in a function called &lt;code&gt;mapLimit&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;mapLimit&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;async/mapLimit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;asyncify&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;async/asyncify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="nx"&gt;items&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;responses&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;mapLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;asyncify&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&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="nx"&gt;fetchAndRetryIfNecessary&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;callTheAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&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;After many 10-request runs, 5.5 seconds was about the best case, slower than even the serial runs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;⏱️ Running Benchmark Parallel with &lt;span class="sb"&gt;`&lt;/span&gt;async.mapLimit&lt;span class="sb"&gt;`&lt;/span&gt;
Request Start: 0 attempt:0 2021-03-29T17:20:42.144Z
Request Start: 1 attempt:0 2021-03-29T17:20:42.151Z
Request Start: 2 attempt:0 2021-03-29T17:20:42.151Z
Request Start: 3 attempt:0 2021-03-29T17:20:42.152Z
Request Start: 4 attempt:0 2021-03-29T17:20:42.152Z
Request Start: 5 attempt:0 2021-03-29T17:20:42.153Z
Request End:   1 attempt:0 200 454ms
Request Start: 6 attempt:0 2021-03-29T17:20:42.605Z
Request End:   6 attempt:0 429 11ms
❗ Retrying:   6 attempt:1 at Mon, 29 Mar 2021 17:20:47 GMT &lt;span class="nb"&gt;sleep &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;4384 ms
Request End:   5 attempt:0 200 571ms
Request Start: 7 attempt:0 2021-03-29T17:20:42.723Z
Request End:   7 attempt:0 429 15ms
❗ Retrying:   7 attempt:1 at Mon, 29 Mar 2021 17:20:47 GMT &lt;span class="nb"&gt;sleep &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;4262 ms
Request End:   2 attempt:0 200 728ms
Request Start: 8 attempt:0 2021-03-29T17:20:42.879Z
Request End:   8 attempt:0 429 12ms
❗ Retrying:   8 attempt:1 at Mon, 29 Mar 2021 17:20:47 GMT &lt;span class="nb"&gt;sleep &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;4109 ms
Request End:   4 attempt:0 200 891ms
Request Start: 9 attempt:0 2021-03-29T17:20:43.044Z
Request End:   9 attempt:0 429 12ms
❗ Retrying:   9 attempt:1 at Mon, 29 Mar 2021 17:20:47 GMT &lt;span class="nb"&gt;sleep &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;3944 ms
Request End:   3 attempt:0 200 1039ms
Request End:   0 attempt:0 200 1163ms
Request Start: 6 attempt:1 2021-03-29T17:20:47.005Z
Request Start: 7 attempt:1 2021-03-29T17:20:47.006Z
Request Start: 8 attempt:1 2021-03-29T17:20:47.007Z
Request Start: 9 attempt:1 2021-03-29T17:20:47.007Z
Request End:   8 attempt:1 200 249ms
Request End:   9 attempt:1 200 394ms
Request End:   6 attempt:1 200 544ms
Request End:   7 attempt:1 200 671ms
✅ Total Parallel with &lt;span class="sb"&gt;`&lt;/span&gt;async.mapLimit&lt;span class="sb"&gt;`&lt;/span&gt;: 5534ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At 20 requests, it finished in about 16 seconds. The upside is that it does not suffer from the retry death spiral we saw in the previous parallel implementation! But it's still slow. Let's keep digging.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;⏱️ Running Benchmark Parallel with &lt;span class="sb"&gt;`&lt;/span&gt;async.mapLimit&lt;span class="sb"&gt;`&lt;/span&gt;
Request Start: 0 attempt:0 2021-03-29T17:25:21.166Z
Request Start: 1 attempt:0 2021-03-29T17:25:21.173Z
Request Start: 2 attempt:0 2021-03-29T17:25:21.173Z
Request Start: 3 attempt:0 2021-03-29T17:25:21.174Z
Request Start: 4 attempt:0 2021-03-29T17:25:21.174Z
Request Start: 5 attempt:0 2021-03-29T17:25:21.174Z
Request End:   0 attempt:0 200 429ms
Request Start: 6 attempt:0 2021-03-29T17:25:21.596Z
Request End:   6 attempt:0 429 19ms
❗ Retrying:   6 attempt:1 at Mon, 29 Mar 2021 17:25:27 GMT &lt;span class="nb"&gt;sleep &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;5385 ms
Request End:   5 attempt:0 200 539ms
Request Start: 7 attempt:0 2021-03-29T17:25:21.714Z
Request End:   7 attempt:0 429 13ms
❗ Retrying:   7 attempt:1 at Mon, 29 Mar 2021 17:25:27 GMT &lt;span class="nb"&gt;sleep &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;5273 ms
Request End:   2 attempt:0 200 664ms
Request Start: 8 attempt:0 2021-03-29T17:25:21.837Z
Request End:   8 attempt:0 429 10ms
❗ Retrying:   8 attempt:1 at Mon, 29 Mar 2021 17:25:27 GMT &lt;span class="nb"&gt;sleep &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;5152 ms
Request End:   1 attempt:0 200 1068ms
Request Start: 9 attempt:0 2021-03-29T17:25:22.241Z

.... more lines ....

❗ Retrying:   17 attempt:2 at Mon, 29 Mar 2021 17:25:37 GMT &lt;span class="nb"&gt;sleep &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;3987 ms
Request Start: 19 attempt:1 2021-03-29T17:25:37.001Z
Request Start: 17 attempt:2 2021-03-29T17:25:37.002Z
Request End:   19 attempt:1 200 182ms
Request End:   17 attempt:2 200 318ms
✅ Total Parallel with &lt;span class="sb"&gt;`&lt;/span&gt;async.mapLimit&lt;span class="sb"&gt;`&lt;/span&gt;: 16154ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Approach 4: winning with a token bucket
&lt;/h2&gt;

&lt;p&gt;So far none of the approaches have been optimal. They have all been slow, triggered many retries, or both.&lt;/p&gt;

&lt;p&gt;The ideal scenario that would get us close to our theoretical minimum time of 3.2 seconds for 10 requests would be to only attempt 6 requests for each 3 second time window. e.g.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Burst 6 requests in parallel&lt;/li&gt;
&lt;li&gt;Wait until the frame resets&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GOTO&lt;/code&gt; 1&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;429&lt;/code&gt; error handling is nice and we will keep it, but we should treat it as an exceptional case as it is unnecessary work. The goal here is to make all the requests without triggering a retry under common circumstances.&lt;/p&gt;

&lt;p&gt;Enter the &lt;a href="https://en.wikipedia.org/wiki/Token_bucket"&gt;token bucket algorithm&lt;/a&gt;. Our desired behavior is its intended purpose: you have &lt;code&gt;n&lt;/code&gt; tokens to spend over some time window—in our case 6 tokens over 3 seconds. Once all tokens are spent, you need to wait the window duration to receive a new set of tokens.&lt;/p&gt;

&lt;p&gt;Here is a simple implementation of a token bucket for our specific purpose. It will count up until it hits the &lt;code&gt;maxRequests&lt;/code&gt;, any requests beyond that will wait the &lt;code&gt;maxRequestWindowMS&lt;/code&gt;, then attempt to acquire the token again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;TokenBucketRateLimiter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;constructor&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;maxRequests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxRequestWindowMS&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxRequests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;maxRequests&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxRequestWindowMS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;maxRequestWindowMS&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;reset&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resetTimeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;scheduleReset&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Only the first token in the set triggers the resetTimeout&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resetTimeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resetTimeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxRequestWindowMS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;acquireToken&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scheduleReset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxRequests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxRequestWindowMS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;acquireToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;nextTick&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;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's try it out!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="nx"&gt;items&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;tokenBucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;TokenBucketRateLimiter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;maxRequests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maxRequestWindowMS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&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;promises&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&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="nx"&gt;fetchAndRetryIfNecessary&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="nx"&gt;tokenBucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;acquireToken&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;callTheAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;promises&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With 10 requests it's about 4 seconds. The best so far, and with no retries!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;⏱️ Running Benchmark Parallel with a token bucket
Request Start: 0 attempt:0 2021-03-29T01:14:17.700Z
Request Start: 1 attempt:0 2021-03-29T01:14:17.707Z
Request Start: 2 attempt:0 2021-03-29T01:14:17.708Z
Request Start: 3 attempt:0 2021-03-29T01:14:17.709Z
Request Start: 4 attempt:0 2021-03-29T01:14:17.709Z
Request Start: 5 attempt:0 2021-03-29T01:14:17.710Z
Request End:   2 attempt:0 200 301ms
Request End:   4 attempt:0 200 411ms
Request End:   5 attempt:0 200 568ms
Request End:   3 attempt:0 200 832ms
Request End:   0 attempt:0 200 844ms
Request End:   1 attempt:0 200 985ms
Request Start: 6 attempt:0 2021-03-29T01:14:20.916Z
Request Start: 7 attempt:0 2021-03-29T01:14:20.917Z
Request Start: 8 attempt:0 2021-03-29T01:14:20.918Z
Request Start: 9 attempt:0 2021-03-29T01:14:20.918Z
Request End:   8 attempt:0 200 223ms
Request End:   6 attempt:0 200 380ms
Request End:   9 attempt:0 200 522ms
Request End:   7 attempt:0 200 661ms
✅ Total Parallel with token bucket: 3992ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And 20 requests? It takes about 10 seconds total. The whole run is super clean with no retries. This is exactly the behavior we are looking for!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;⏱️ Running Benchmark Parallel with a token bucket
Request Start: 0 attempt:0 2021-03-29T22:30:51.321Z
Request Start: 1 attempt:0 2021-03-29T22:30:51.329Z
Request Start: 2 attempt:0 2021-03-29T22:30:51.329Z
Request Start: 3 attempt:0 2021-03-29T22:30:51.330Z
Request Start: 4 attempt:0 2021-03-29T22:30:51.330Z
Request Start: 5 attempt:0 2021-03-29T22:30:51.331Z
Request End:   5 attempt:0 200 354ms
Request End:   2 attempt:0 200 507ms
Request End:   3 attempt:0 200 624ms
Request End:   4 attempt:0 200 969ms
Request End:   0 attempt:0 200 980ms
Request End:   1 attempt:0 200 973ms
Request Start: 6 attempt:0 2021-03-29T22:30:54.538Z
Request Start: 7 attempt:0 2021-03-29T22:30:54.539Z
Request Start: 8 attempt:0 2021-03-29T22:30:54.540Z
Request Start: 9 attempt:0 2021-03-29T22:30:54.541Z
Request Start: 10 attempt:0 2021-03-29T22:30:54.541Z
Request Start: 11 attempt:0 2021-03-29T22:30:54.542Z
Request End:   8 attempt:0 200 270ms
Request End:   10 attempt:0 200 396ms
Request End:   6 attempt:0 200 525ms
Request End:   7 attempt:0 200 761ms
Request End:   11 attempt:0 200 762ms
Request End:   9 attempt:0 200 870ms
Request Start: 12 attempt:0 2021-03-29T22:30:57.746Z
Request Start: 13 attempt:0 2021-03-29T22:30:57.746Z
Request Start: 14 attempt:0 2021-03-29T22:30:57.747Z
Request Start: 15 attempt:0 2021-03-29T22:30:57.748Z
Request Start: 16 attempt:0 2021-03-29T22:30:57.748Z
Request Start: 17 attempt:0 2021-03-29T22:30:57.749Z
Request End:   15 attempt:0 200 340ms
Request End:   13 attempt:0 200 461ms
Request End:   17 attempt:0 200 581ms
Request End:   16 attempt:0 200 816ms
Request End:   12 attempt:0 200 823ms
Request End:   14 attempt:0 200 962ms
Request Start: 18 attempt:0 2021-03-29T22:31:00.954Z
Request Start: 19 attempt:0 2021-03-29T22:31:00.955Z
Request End:   19 attempt:0 200 169ms
Request End:   18 attempt:0 200 294ms
✅ Total Parallel with a token bucket: 10047ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Approach 4.1: using someone else's token bucket
&lt;/h2&gt;

&lt;p&gt;The token bucket implementation above was for demonstration purposes. In production, you might not want to maintain your own token bucket if you can help it.&lt;/p&gt;

&lt;p&gt;If you're using node, there is a node module called &lt;a href="https://www.npmjs.com/package/limiter"&gt;limiter&lt;/a&gt; that implements token bucket behavior. The library is more general than our &lt;code&gt;TokenBucketRateLimiter&lt;/code&gt; class above, but we can use it to achieve the exact same behavior:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RateLimiter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;limiter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;LimiterLibraryRateLimiter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;constructor&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;maxRequests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxRequestWindowMS&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxRequests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;maxRequests&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxRequestWindowMS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;maxRequestWindowMS&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limiter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;RateLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxRequests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxRequestWindowMS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&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="nx"&gt;acquireToken&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tryRemoveTokens&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="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;nextTick&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;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxRequestWindowMS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;acquireToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage is exactly the same as the previous example, just swap &lt;code&gt;LimiterLibraryRateLimiter&lt;/code&gt; in place of &lt;code&gt;TokenBucketRateLimiter&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="nx"&gt;items&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;rateLimiter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;LimiterLibraryRateLimiter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;maxRequests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maxRequestWindowMS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&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;promises&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&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="nx"&gt;fetchAndRetryIfNecessary&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="nx"&gt;rateLimiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;acquireToken&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;callTheAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;promises&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Other considerations
&lt;/h2&gt;

&lt;p&gt;With the token bucket in the two approaches above, we have a workable solution for consuming APIs with rate-limits in production. Depending on your architecture there may be some other considerations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Success rate-limit headers
&lt;/h3&gt;

&lt;p&gt;APIs with rate-limits often return &lt;a href="https://stackoverflow.com/a/16022625"&gt;rate-limit headers&lt;/a&gt; on a successful request. e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;HTTP: 200
X-Ratelimit-Limit: 40         &lt;span class="c"&gt;# Number of total requests in the window&lt;/span&gt;
X-Ratelimit-Remaining: 30     &lt;span class="c"&gt;# Number of remaining requests in the window&lt;/span&gt;
X-Ratelimit-Reset: 1617054237 &lt;span class="c"&gt;# Seconds since epoch til reset of the window&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The header names are convention &lt;a href="https://tools.ietf.org/id/draft-polli-ratelimit-headers-00.html#header-specifications"&gt;at the time of writing&lt;/a&gt;, but many APIs use the headers specified above.&lt;/p&gt;

&lt;p&gt;You could run your token bucket with the value from these headers rather than keep state in your API client.&lt;/p&gt;

&lt;h3&gt;
  
  
  Throttling in a distributed system
&lt;/h3&gt;

&lt;p&gt;If you have multiple nodes making requests to a rate-limited API, storing the token bucket state locally on a single node will not work. A couple options to minimize the number of retries might be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;X-Ratelimit headers&lt;/strong&gt;: Using the headers described above&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared state&lt;/strong&gt;: You could keep the token bucket state in something available to all nodes like &lt;code&gt;redis&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Verdict: use a token bucket
&lt;/h2&gt;

&lt;p&gt;Hopefully it's clear that using a token bucket is the best way to implement API throttling. Overall this implementation is clean, scalable, and about as fast as we can go without triggering retries. And if there is a retry? You’re covered by the &lt;code&gt;429 Too Many Requests&lt;/code&gt; handling discussed in the beginning.&lt;/p&gt;

&lt;p&gt;Even if you don't use JavaScript, the ideas discussed here are transferable to any language. Feel free to re-implement the &lt;code&gt;TokenBucketRateLimiter&lt;/code&gt; above in your favorite language if you can’t find a suitable alternative!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: check out the &lt;a href="https://gist.github.com/benogle/723e60aa020f85ad6adc8e7beb70e705"&gt;example script&lt;/a&gt; I used to run these benchmarks. You should be able to use it against your own API by putting your request code into the &lt;code&gt;callTheAPI&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;If you have questions, please do not hesitate to contact us at: &lt;a href="mailto:developers@useanvil.com"&gt;developers@useanvil.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
