<?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: Jules Smeets</title>
    <description>The latest articles on DEV Community by Jules Smeets (@juulsme).</description>
    <link>https://dev.to/juulsme</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3796399%2F102ca81b-0ce8-492a-b4c9-73147401feb8.jpeg</url>
      <title>DEV Community: Jules Smeets</title>
      <link>https://dev.to/juulsme</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/juulsme"/>
    <language>en</language>
    <item>
      <title>JWTs in Elixir: fast parsing by plain pattern matching</title>
      <dc:creator>Jules Smeets</dc:creator>
      <pubDate>Thu, 26 Mar 2026 21:35:01 +0000</pubDate>
      <link>https://dev.to/juulsme/jwts-in-elixir-fast-parsing-by-plain-pattern-matching-52n5</link>
      <guid>https://dev.to/juulsme/jwts-in-elixir-fast-parsing-by-plain-pattern-matching-52n5</guid>
      <description>&lt;p&gt;JSON Web Tokens (JWTs) are the backbone of modern authentication. If you are building an API, chances are you are verifying a JWT on almost every single incoming request.&lt;/p&gt;

&lt;p&gt;Because it happens so frequently, JWT verification is a prime candidate for optimization. But if you look at how standard JWT verification works, there is a lot of hidden overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Standard "Slow Path"
&lt;/h3&gt;

&lt;p&gt;A JWT consists of three parts separated by dots: &lt;code&gt;header.payload.signature&lt;/code&gt;. To verify a standard token, your application typically has to do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Split the string by the &lt;code&gt;.&lt;/code&gt; character using &lt;code&gt;String.split/2&lt;/code&gt; and/or regex.&lt;/li&gt;
&lt;li&gt;Base64-decode the header.&lt;/li&gt;
&lt;li&gt;Parse the resulting JSON string into a map.&lt;/li&gt;
&lt;li&gt;Extract the &lt;code&gt;kid&lt;/code&gt; (Key ID) and &lt;code&gt;alg&lt;/code&gt; (Algorithm) claims.&lt;/li&gt;
&lt;li&gt;Look up the correct public or symmetric key.&lt;/li&gt;
&lt;li&gt;Verify the signature against the payload.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Steps 1 through 4 require allocating memory for new binaries, running a Base64 decoder, and firing up a JSON parser—all just to figure out &lt;em&gt;which key&lt;/em&gt; to use. &lt;/p&gt;

&lt;p&gt;If your application is minting its own tokens, 99% of the incoming requests are going to use your current, active signing key. Doing all that decoding and parsing on every request is wasted effort.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our own header is always the same
&lt;/h3&gt;

&lt;p&gt;The realization that speeds this up is simple: &lt;strong&gt;if you control the active signing key, the JWT header is entirely predictable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of extracting the token's header to find the key, we can flip the process upside down. We can pre-calculate exactly what the Base64-encoded header will look like for our active key, and use it to fast-track both signing and verifying tokens.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup: Caching the Header
&lt;/h3&gt;

&lt;p&gt;The first step is to calculate the expected header at startup (or upon key rotation) and cache it. We use Erlang's &lt;code&gt;:persistent_term&lt;/code&gt; for blazing fast, concurrent read access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MyApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Jwt&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nv"&gt;@doc&lt;/span&gt; &lt;span class="s2"&gt;"Run this at application startup or when rotating keys"&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;setup_fast_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alg&lt;/span&gt; &lt;span class="p"&gt;\\&lt;/span&gt; &lt;span class="s2"&gt;"HS256"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expected_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="c1"&gt;# we need deterministic JSON with predictable key ordering&lt;/span&gt;
      &lt;span class="sx"&gt;~s({"alg":"#{alg}","typ":"JWT","kid":"#{kid}"})&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url_encode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;padding:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="ss"&gt;:persistent_term&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:jwt_header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fast-track verification
&lt;/h3&gt;

&lt;p&gt;The real magic happens when checking incoming requests. We use Elixir's binary pattern matching to instantly route the token based on whether it starts with our cached header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;expected_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:persistent_term&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:jwt_header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# Does the token start with our exact pre-calculated header?&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;^&lt;/span&gt;&lt;span class="n"&gt;expected_header&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;?.&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload_and_sig&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; 
      &lt;span class="n"&gt;fast_verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload_and_sig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# ... slow path fallback ...&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;slow_verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The binary pattern matching is Elixir at its finest. The VM doesn't need to split strings or allocate new memory to do so. It simply looks at the bytes of the incoming token, checks if they match the pinned &lt;code&gt;expected_header&lt;/code&gt; variable followed by a &lt;code&gt;.&lt;/code&gt; character, and assigns the rest of the binary to &lt;code&gt;payload_and_sig&lt;/code&gt;. We now know the signing key and algorithm without parsing the header!&lt;/p&gt;

&lt;p&gt;We are not done, however, and can take pattern matching one step further to split the payload and signature. This relies on two things: we know the signature's length in advance and can use &lt;code&gt;byte_size/1&lt;/code&gt; to determine the length of the whole (in constant time!):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# For HS256, the encoded signature is always exactly 43 characters&lt;/span&gt;
&lt;span class="nv"&gt;@hs256_sig_len&lt;/span&gt; &lt;span class="mi"&gt;43&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;fast_verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload_and_sig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;payload_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;byte_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload_and_sig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;@hs256_sig_len&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;case&lt;/span&gt; &lt;span class="n"&gt;payload_and_sig&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;enc_payload&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload_len&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sx"&gt;?.&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enc_sig&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@hs256_sig_len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="c1"&gt;# We skipped String.split/2 entirely!&lt;/span&gt;
      &lt;span class="c1"&gt;# Now we just decode the payload and verify the signature...&lt;/span&gt;
      &lt;span class="n"&gt;do_crypto_verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enc_payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enc_sig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&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="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:malformed_token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Signing
&lt;/h3&gt;

&lt;p&gt;Of course, we can also use our precomputed header to speed up token signing (also leveraging iolists to prevent creating unnecessary intermediate binaries):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:persistent_term&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:jwt_header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;enc_payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode!&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url_encode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;padding:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;mac_base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;?.&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enc_payload&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:crypto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:hmac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:sha256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mac_base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url_encode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;padding:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iodata_to_binary&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;mac_base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;?.&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What if the header is not fully deterministic? Poly1305 and Base64 Magic
&lt;/h3&gt;

&lt;p&gt;The optimization above works flawlessly for algorithms like HS256 or EdDSA. But what if you are using a MAC generated via Poly1305? &lt;/p&gt;

&lt;p&gt;Poly1305 requires a unique &lt;code&gt;nonce&lt;/code&gt; for every single invocation to remain secure. This means our JWT header suddenly needs to look like this:&lt;br&gt;
&lt;code&gt;{"alg":"Poly1305","typ":"JWT","nonce":"&amp;lt;random_string&amp;gt;","kid":"my_key"}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Because the &lt;code&gt;nonce&lt;/code&gt; changes every time, we can no longer pre-calculate the &lt;em&gt;entire&lt;/em&gt; Base64 encoded header. Does this mean we have to fall back to the slow path? Not if we use a little Base64 math.&lt;/p&gt;

&lt;p&gt;Base64 works by encoding 3 bytes of raw data into 4 characters. If a string's byte length is exactly a multiple of 3, it encodes cleanly without padding (&lt;code&gt;=&lt;/code&gt;) and without carrying over bits into the next character block. Such encoded strings can then be safely concatenated without invalidating the encoding!&lt;/p&gt;

&lt;p&gt;We can exploit this by carefully crafting our JSON header so the static parts are multiples of 3 bytes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: &lt;code&gt;{"alg":"Poly1305","typ":"JWT","nonce":"&lt;/code&gt; (Length is 39, and 39 / 3 = 13. Perfect!)&lt;/li&gt;
&lt;li&gt;Part 2: &lt;code&gt;&amp;lt;dynamic_nonce&amp;gt;",&lt;/code&gt; (The nonce is 12 bytes, which encodes to 16 bytes. We add &lt;code&gt;",&lt;/code&gt; and part 2 is now 18 bytes - dividable by 3!)&lt;/li&gt;
&lt;li&gt;Part 3: &lt;code&gt;"kid":&lt;/code&gt; (Length is 6!)&lt;/li&gt;
&lt;li&gt;Part 4: &lt;code&gt;"my_key"}&lt;/code&gt; (The last part has no length requirement because nothing will be concatenated to its tail)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because Parts 1, 2 and 3 are multiples of 3, we can safely pre-calculate their Base64 equivalents and concatenate them with the encoded dynamic bits later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Pre-encoded static chunks (safe to concatenate)&lt;/span&gt;
&lt;span class="nv"&gt;@p1305_h&lt;/span&gt; &lt;span class="sx"&gt;~s({"alg":"Poly1305","typ":"JWT","nonce":")&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url_encode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;padding:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;@p1305_kid_seg&lt;/span&gt; &lt;span class="sx"&gt;~s("kid":)&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url_encode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;padding:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;setup_poly1305_fast_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;exp_htail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;~s("#{kid}"})&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url_encode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;padding:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="ss"&gt;:persistent_term&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:jwt_p1305_header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exp_htail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can add a new pattern match to our &lt;code&gt;verify/1&lt;/code&gt; function to catch Poly1305 tokens. We just extract the encoded &lt;code&gt;nonce_seg&lt;/code&gt; right out of the middle of the binary string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;exp_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:persistent_term&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:jwt_header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;exp_htail&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:persistent_term&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:jwt_p1305_header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# 1. The Simple Fast Path (HS256, etc)&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;^&lt;/span&gt;&lt;span class="n"&gt;exp_header&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;?.&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload_and_sig&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; 
      &lt;span class="n"&gt;fast_verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload_and_sig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 2. The Base64 Magic Fast Path (Poly1305)&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;@p1305_h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nonce_seg&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;@p1305_kid_seg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;exp_htail&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;?.&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload_and_sig&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;fast_p1305_verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nonce_seg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload_and_sig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 3. THE SLOW PATH&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; 
      &lt;span class="n"&gt;slow_verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By ensuring our JSON keys and values add up to multiples of 3, we can continue to use Elixir's blazing fast binary pattern matching to route tokens, skipping the JSON parser entirely even when parts of the header are highly dynamic.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fallback
&lt;/h3&gt;

&lt;p&gt;Of course, the slow path cannot be completely removed. If you rotate your signing keys, users with valid tokens signed by the &lt;em&gt;old&lt;/em&gt; key will still need to access your API. &lt;/p&gt;

&lt;p&gt;Because their tokens will have a different &lt;code&gt;kid&lt;/code&gt; in the header, the fast path pattern match will naturally fail. The &lt;code&gt;case&lt;/code&gt; statement falls back to &lt;code&gt;slow_verify&lt;/code&gt;, which does the traditional Base64 decoding, JSON parsing, and dynamic key lookup. &lt;/p&gt;

&lt;p&gt;The beauty of this architecture is that the fast path handles 99% of your traffic with near-zero overhead, while the slow path gracefully catches edge cases, legacy tokens, and key rotations without the caller ever needing to know the difference. And although we don't parse the headers, we don't assume anything here; because of the binary pattern matching, there's not a single header byte that is not exactly what we expect it to be. JWT-verification speed is improved by about 30% for normal-sized JWTs.&lt;/p&gt;

&lt;p&gt;A real-world example (using only OTP!) that supports key rotation and multiple signing algorithms including HMAC-SHA256, EdDSA and Poly1305 can be found in Charon's &lt;a href="https://github.com/weareyipyip/charon/blob/main/lib/charon/token_factory/jwt.ex" rel="noopener noreferrer"&gt;JWT token factory&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>jwt</category>
    </item>
    <item>
      <title>Why We Stopped Using UUIDv4 in Elixir (And What We Built Instead)</title>
      <dc:creator>Jules Smeets</dc:creator>
      <pubDate>Fri, 27 Feb 2026 18:14:21 +0000</pubDate>
      <link>https://dev.to/juulsme/why-we-stopped-using-uuidv4-in-elixir-and-what-we-built-instead-59i3</link>
      <guid>https://dev.to/juulsme/why-we-stopped-using-uuidv4-in-elixir-and-what-we-built-instead-59i3</guid>
      <description>&lt;p&gt;If you’re building a new Phoenix application today, deciding on a primary key strategy usually takes about three seconds. You don't want to use standard auto-incrementing integers because they leak your business volume (e.g., "Oh, I'm only user #104? This app is a ghost town").&lt;/p&gt;

&lt;p&gt;So, you do what everyone does: you reach for &lt;code&gt;Ecto.UUID&lt;/code&gt;. It's built-in, it's unpredictable, and it solves the problem.&lt;/p&gt;

&lt;p&gt;But as your application scales, UUIDv4 becomes a silent performance killer. After wrestling with database bloat and terrible API developer experience, we decided to build a better way. Enter &lt;strong&gt;&lt;a href="https://hexdocs.pm/once" rel="noopener noreferrer"&gt;Once&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is why we ditched UUIDs, and how you can get Stripe-style, high-performance IDs in your Elixir app in about two minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem with UUIDv4: Database Fragmentation
&lt;/h3&gt;

&lt;p&gt;PostgreSQL (like most relational databases) uses B-trees for its indexes. B-trees are incredibly fast, but they have a distinct preference: they absolutely &lt;em&gt;love&lt;/em&gt; sequential data.&lt;/p&gt;

&lt;p&gt;When you insert a sequential ID (like a standard integer or a time-sorted Snowflake ID), Postgres cleanly appends it to the right edge of the index.&lt;/p&gt;

&lt;p&gt;UUIDv4, however, is purely random. When you insert a million random UUIDs, Postgres has to constantly search for the correct leaf node to insert the new value. This causes massive "page splits," fragments the index, and destroys your cache locality.&lt;/p&gt;

&lt;p&gt;Over time, your index size balloons, your write I/O skyrockets, and your database spends precious memory caching index pages that are mostly empty.&lt;/p&gt;

&lt;h3&gt;
  
  
  The API UX Problem: "Type Confusion"
&lt;/h3&gt;

&lt;p&gt;Beyond database performance, UUIDs are completely hostile to human developers.&lt;/p&gt;

&lt;p&gt;If you are looking at a log file or an API payload and see &lt;code&gt;9f1c0d3a-2b7e-49a1-8d5c-9123456789ab&lt;/code&gt;, what is it? Is it a User ID? A Payment ID? A Subscription ID? If a frontend developer accidentally passes an Organization ID into a User endpoint, the system will just return a generic &lt;code&gt;404 Not Found&lt;/code&gt;, leading to hours of frustrating debugging.&lt;/p&gt;

&lt;p&gt;Stripe solved this years ago by prefixing their IDs: &lt;code&gt;cus_...&lt;/code&gt; for customers, &lt;code&gt;ch_...&lt;/code&gt; for charges. It is self-documenting, type-safe, and universally loved by developers. We wanted that exact experience in Ecto.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enter &lt;code&gt;Once&lt;/code&gt;: The Best of Both Worlds
&lt;/h3&gt;

&lt;p&gt;We built &lt;a href="https://github.com/juulSme/Once" rel="noopener noreferrer"&gt;Once&lt;/a&gt; as a custom Ecto type to solve both the database performance problem and the API usability problem, without compromising on security.&lt;/p&gt;

&lt;p&gt;Under the hood, &lt;code&gt;Once&lt;/code&gt; is powered by &lt;a href="https://github.com/juulSme/NoNoncense" rel="noopener noreferrer"&gt;NoNoncense&lt;/a&gt;, a distributed, lock-free ID generator that uses Erlang's &lt;code&gt;:atomics&lt;/code&gt; to generate tens of millions of 64-bit IDs per second. Because it's 64-bit (instead of UUID's 128-bit), it immediately cuts your ID storage footprint in half.&lt;/p&gt;

&lt;h4&gt;
  
  
  The "Masking" Magic
&lt;/h4&gt;

&lt;p&gt;You usually have to make a painful choice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use sequential integers (Fast database, but leaks business metrics).&lt;/li&gt;
&lt;li&gt;Use random strings (Protects metrics, but slow database).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With &lt;code&gt;Once&lt;/code&gt; masking, you don't have to choose. You configure your Ecto schema to generate a sequential, counter-based ID. When Ecto saves the record to your database, it stores a raw, 64-bit integer. Your database is perfectly happy, and your B-tree indexes stay perfectly compact.&lt;/p&gt;

&lt;p&gt;But when Ecto &lt;em&gt;loads&lt;/em&gt; that record back into your Elixir application, &lt;code&gt;Once&lt;/code&gt; uses blazing-fast encryption to transparently mask the integer, and prepends your chosen prefix.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;As a standard bigint in your database:&lt;/strong&gt; &lt;code&gt;98770186085072901&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In your Elixir App / JSON API:&lt;/strong&gt; &lt;code&gt;"usr_4Tnk9-GqJ-s"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your database gets perfectly optimized sequential writes. Your API consumers get unpredictable, Stripe-style, self-documenting strings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fully Composable: Pick What You Need
&lt;/h3&gt;

&lt;p&gt;The best part about &lt;code&gt;Once&lt;/code&gt; is that &lt;strong&gt;these features are fully composable and optional.&lt;/strong&gt; You can tailor the ID generation to your exact needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Just want unique IDs?&lt;/strong&gt; Use the defaults. You get 64-bit integers that are incredibly fast to generate and index in your database, and url64-encoded strings like &lt;code&gt;"AV7m9gAAAAU"&lt;/code&gt; in Elixir.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Need chronological sorting?&lt;/strong&gt; Change to &lt;code&gt;nonce_type: :sortable&lt;/code&gt;. You get a Snowflake-like ID where the first 42 bits are a timestamp, meaning &lt;code&gt;ORDER BY id&lt;/code&gt; works perfectly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Want masked IDs?&lt;/strong&gt; Just add &lt;code&gt;mask: true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Need true unpredictability at rest?&lt;/strong&gt; If you don't want to use masking and genuinely need UUIDv4-style randomness stored in your database, switch to &lt;code&gt;nonce_type: :encrypted&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Want Prefixes?&lt;/strong&gt; Just add &lt;code&gt;prefix: "req_"&lt;/code&gt; to any of the above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Want to persist the prefix?&lt;/strong&gt; Use a binary database format like &lt;code&gt;db_format: :hex&lt;/code&gt; and add &lt;code&gt;persist_prefix: true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Want a different API format like integers?&lt;/strong&gt; Use &lt;code&gt;ex_format: :unsigned&lt;/code&gt; to render IDs like &lt;code&gt;"usr_98770186085072901"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can combine all of these as you see fit, and automagically generate distributed IDs with just the features that you need and nothing more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not UUIDv7?
&lt;/h3&gt;

&lt;p&gt;While UUIDv7 solves the index fragmentation problem, it's not as flexible as &lt;code&gt;Once&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Storage:&lt;/strong&gt; UUIDv7 is still a bloated 128-bit identifier. Once cuts that in half (64-bit).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API UX:&lt;/strong&gt; UUIDv7 still looks like a massive, unreadable string (018e4b...). Once gives you Stripe-style usr_... prefixes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business Leakage/Security:&lt;/strong&gt; UUIDv7 visibly leaks the exact millisecond a record was created. Once uses masking to hide the underlying counter/timestamp entirely.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The 2-Minute Phoenix Quickstart
&lt;/h3&gt;

&lt;p&gt;Here is how easy it is to drop into a Phoenix project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Add the dependencies to &lt;code&gt;mix.exs&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:once&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.2"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Initialize the generator in your &lt;code&gt;application.ex&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create a unique Machine ID for this node (supports up to 512 nodes in a cluster)&lt;/span&gt;
&lt;span class="n"&gt;machine_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;NoNoncense&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;MachineId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;node_list:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize a NoNoncense instance for Once with a secret key for masking&lt;/span&gt;
&lt;span class="c1"&gt;# (Pull this from your environment in production!)&lt;/span&gt;
&lt;span class="n"&gt;secret_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ONCE_SECRET_KEY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"my supersecret dev key"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;NoNoncense&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="no"&gt;Once&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;machine_id:&lt;/span&gt; &lt;span class="n"&gt;machine_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;base_key:&lt;/span&gt; &lt;span class="n"&gt;secret_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;NoNoncense&lt;/code&gt; is not a GenServer; it keeps its write-once-read-often state in &lt;code&gt;:persistent_term&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Define your Ecto Schema:&lt;/strong&gt;&lt;br&gt;
Just set &lt;code&gt;Once&lt;/code&gt; as your primary key, give it a prefix, and turn on masking.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MyApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Accounts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;

  &lt;span class="nv"&gt;@primary_key&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Once&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;autogenerate:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;prefix:&lt;/span&gt; &lt;span class="s2"&gt;"usr_"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;mask:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
    &lt;span class="n"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it. You now have locally unique (within your app or domain), Stripe-prefixed, cryptographically masked 64-bit IDs that won't destroy your database indexes.&lt;/p&gt;

&lt;p&gt;If you’re tired of the UUID performance tax or just want a better developer experience for your API consumers, give &lt;code&gt;Once&lt;/code&gt; a try on your next project.&lt;/p&gt;

&lt;p&gt;Check out the full documentation and advanced configuration options on &lt;a href="https://hexdocs.pm/once" rel="noopener noreferrer"&gt;HexDocs for Once&lt;/a&gt; and &lt;a href="https://hexdocs.pm/no_noncense" rel="noopener noreferrer"&gt;NoNoncense&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>database</category>
      <category>distributedsystems</category>
    </item>
  </channel>
</rss>
