<?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: Ely</title>
    <description>The latest articles on DEV Community by Ely (@edeckers).</description>
    <link>https://dev.to/edeckers</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%2F3559082%2F048052b3-6c42-4db8-bd63-ceee3556673a.jpeg</url>
      <title>DEV Community: Ely</title>
      <link>https://dev.to/edeckers</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/edeckers"/>
    <language>en</language>
    <item>
      <title>Building an MCP server: teaching AI assistants about backups</title>
      <dc:creator>Ely</dc:creator>
      <pubDate>Sun, 15 Feb 2026 19:30:54 +0000</pubDate>
      <link>https://dev.to/edeckers/building-my-first-mcp-server-teaching-ai-assistants-about-backups-21g5</link>
      <guid>https://dev.to/edeckers/building-my-first-mcp-server-teaching-ai-assistants-about-backups-21g5</guid>
      <description>&lt;p&gt;&lt;em&gt;My journey into the Model Context Protocol&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Today I built a Model Context Protocol (MCP) server to connect AI assistants with &lt;a href="https://github.com/bareos/bareos" rel="noopener noreferrer"&gt;Bareos backup infrastructure&lt;/a&gt;. Join me as I walk through building this integration and share what I learned along the way.&lt;/p&gt;

&lt;p&gt;Or if you just want to see the result, check it out at &lt;a href="https://github.com/edeckers/bareos-mcp-server" rel="noopener noreferrer"&gt;https://github.com/edeckers/bareos-mcp-server&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2xhn1i1qjbfbce1gmz1p.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2xhn1i1qjbfbce1gmz1p.webp" alt="Photo of a clown puppet" width="800" height="533"&gt;&lt;/a&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@venczakjanos" rel="noopener noreferrer"&gt;János Venczák&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/a-close-up-of-a-statue-of-a-clown-jpZ3jjGs1W0" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt; &lt;/p&gt;

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

&lt;p&gt;The Model Context Protocol is Anthropic's solution for giving AI assistants like Claude access to external tools and data. It's a JSON-RPC based protocol that runs over stdin/stdout.&lt;/p&gt;

&lt;p&gt;The concept is straightforward: instead of manually running &lt;code&gt;bconsole&lt;/code&gt; commands, copying output, and pasting it into a chat, you ask &lt;em&gt;"show me the last 10 backup jobs"&lt;/em&gt; and the AI fetches it directly from your Bareos Director through the MCP server.&lt;/p&gt;

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

&lt;p&gt;I've been managing my backups with Bareos for a while now. And while &lt;code&gt;bconsole&lt;/code&gt; is powerful, it requires remembering specific command syntax for listing jobs, checking storage pools, or viewing client status.&lt;/p&gt;

&lt;p&gt;Being able to query backup infrastructure conversationally, like &lt;em&gt;"why did last night's backup fail?"&lt;/em&gt; or &lt;em&gt;"how much storage is left in the Full pool?"&lt;/em&gt; seemed like a practical use case for MCP.&lt;/p&gt;

&lt;h2&gt;
  
  
  The journey
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Understanding the Protocol
&lt;/h3&gt;

&lt;p&gt;MCP is a JSON-RPC protocol with three main operations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Initialize: Server announces capabilities&lt;/li&gt;
&lt;li&gt;List tools: Server describes available tools&lt;/li&gt;
&lt;li&gt;Call tool: Client executes a tool with arguments&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And that's pretty much it. The rest is tool implementation and error handling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read-Only by design for now
&lt;/h3&gt;

&lt;p&gt;I deliberately kept everything read-only initially, so no starting jobs, deleting volumes, or pruning backups. Just queries. This was a practical decision: I wanted to understand the protocol and tool design without worrying about accidentally breaking production backups. Mutable actions are planned for later versions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Emergent behavior
&lt;/h3&gt;

&lt;p&gt;The interesting part came when I realized Claude could answer questions I hadn't explicitly coded for, and which would require multiple bconsole queries. Ask it "Which clients haven't backed up in the last 24 hours?" and it will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Call &lt;code&gt;list_jobs&lt;/code&gt; to get recent jobs&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;list_clients&lt;/code&gt; to get all clients&lt;/li&gt;
&lt;li&gt;Compare the two&lt;/li&gt;
&lt;li&gt;Provide an answer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I built primitive tools; the AI combines them to answer complex queries. That's the real value of MCP: you're building composable primitives, not a comprehensive API.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tool design matters
&lt;/h3&gt;

&lt;p&gt;The quality of tool descriptions directly impacts how well the AI uses them. Vague descriptions lead to mistakes. Clear, specific descriptions with parameter explanations work reliably.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Don't do this: "List jobs"&lt;/li&gt;
&lt;li&gt;Do this: "List recent backup jobs. Use the limit parameter to control how many jobs are returned (default: 50)"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Testing without an AI assistant
&lt;/h3&gt;

&lt;p&gt;You can test MCP servers with just echo and pipes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | \
./bareos-mcp-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No need to configure Claude Desktop or spin up a full client. Just send JSON-RPC over stdin. It is actually how Claude will interact with the server, and it is very useful for debugging.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build for composition
&lt;/h3&gt;

&lt;p&gt;Don't try to predict every query users might want. Build small, focused tools that can be combined. The AI is surprisingly good at figuring out how to compose them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommendations for building MCP servers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Start with a tool you already use. Pick something you interact with regularly, you'll understand the use cases and catch issues faster.&lt;/li&gt;
&lt;li&gt;Begin with one tool. Get one tool working end-to-end before adding more. I started with &lt;code&gt;list_jobs&lt;/code&gt; and expanded from there.&lt;/li&gt;
&lt;li&gt;Write clear descriptions. Tool descriptions are the AI's only guide.&lt;/li&gt;
&lt;li&gt;Be specific about parameters, formats, and return values.&lt;/li&gt;
&lt;li&gt;Test with raw JSON-RPC first. Verify your tools work with manual calls before involving an AI assistant. Faster iteration, clearer debugging.&lt;/li&gt;
&lt;li&gt;Start read-only if safety matters. Read-only operations let you learn the protocol without risking production systems. Add mutations once you're confident.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;The code is open source at &lt;a href="https://github.com/edeckers/bareos-mcp-server" rel="noopener noreferrer"&gt;https://github.com/edeckers/bareos-mcp-server&lt;/a&gt; with installation instructions for both Claude Code and Claude Desktop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful resources
&lt;/h2&gt;

&lt;p&gt;Building your own MCP server? These resources helped me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;https://modelcontextprotocol.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.anthropic.com/claude/docs" rel="noopener noreferrer"&gt;https://docs.anthropic.com/claude/docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/typescript-sdk" rel="noopener noreferrer"&gt;https://github.com/modelcontextprotocol/typescript-sdk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/servers" rel="noopener noreferrer"&gt;https://github.com/modelcontextprotocol/servers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Built something with MCP or considering it? Let me know in the comments!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the Dutch language doesn't scare you, and you'd like to know more about what keeps me busy aside from my pet projects, check my company website &lt;a href="https://branie.it" rel="noopener noreferrer"&gt;https://branie.it&lt;/a&gt;! Maybe we can work together on something someday :)&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>programming</category>
      <category>devops</category>
    </item>
    <item>
      <title>Building Flipr: a URL shortener, one commit at a time</title>
      <dc:creator>Ely</dc:creator>
      <pubDate>Sun, 09 Nov 2025 21:09:46 +0000</pubDate>
      <link>https://dev.to/edeckers/building-flipr-a-url-shortener-one-commit-at-a-time-gnb</link>
      <guid>https://dev.to/edeckers/building-flipr-a-url-shortener-one-commit-at-a-time-gnb</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 1: the simplest thing that could possibly work&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Welcome to the first post in a series that dives into the world of distributed systems building on Kubernetes, using as a subject &lt;strong&gt;the "Todo application" of systems design&lt;/strong&gt;: a URL shortener we'll call &lt;strong&gt;Flipr 🐬&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The goal is to build an actual, functioning, deployable system, designed to scale from a single instance to a distributed cluster on Kubernetes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj7hgoxzfiwhxvn0w4ong.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj7hgoxzfiwhxvn0w4ong.webp" alt="Photo of a happy dolphin in the water, who I like to think is named Flipr" width="800" height="533"&gt;&lt;/a&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@louangm" rel="noopener noreferrer"&gt;Louan García&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/dolphin-in-body-of-water-Xsq5ie5f498" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  What this series is and what it is not
&lt;/h2&gt;

&lt;p&gt;This series focuses on building a distributed, scalable URL shortener and the architectural decisions behind it. To keep that focus sharp, I'm intentionally omitting several things that would be critical in a production system, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No unit tests:&lt;/strong&gt; focus is on architectural evolution, not a production-ready codebase&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal input validation:&lt;/strong&gt; basic checks only, not production-grade security hardening&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No abuse prevention:&lt;/strong&gt; there will be rate limiting, but anti-spam measures won't be covered&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No user accounts:&lt;/strong&gt; this is a simple shortener demonstrating distributed systems concepts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, if you're looking for a complete, production-ready URL shortener, &lt;a href="https://github.com/topics/url-shortener" rel="noopener noreferrer"&gt;check GitHub and take your pick&lt;/a&gt;. But if you want to understand how distributed systems are built and how to benchmark architectural improvements, you're in the right place!&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements &amp;amp; scale targets
&lt;/h2&gt;

&lt;p&gt;Before we write any code, let's define what we're building toward. I'm intentionally keeping the performance targets vague, because actual throughput and latency will depend entirely on the hardware you deploy to.&lt;/p&gt;

&lt;p&gt;And while we don't define hard performance targets upfront, we'll be benchmarking each iteration and comparing results throughout this series. This way, we can demonstrate that our architectural evolution actually improves performance and scalability, not just adds complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Functional Requirements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Short Code Specifications:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Length: 6-7 characters&lt;/li&gt;
&lt;li&gt;Character set: a-z, A-Z, 0-9 (62 characters)&lt;/li&gt;
&lt;li&gt;Namespace: 62^7 = ~3.5 trillion possible codes&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;URL Constraints:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Max original URL length: 2,048 characters (browser-safe)&lt;/li&gt;
&lt;li&gt;Short code lifetime: Indefinite (no expiration)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Design Goals:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Horizontal Scalability:&lt;/strong&gt; add more instances to handle increased load, rather than relying on bigger servers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read-Heavy workload:&lt;/strong&gt; expect redirects to vastly outnumber shortening requests (typical 10:1 ratio or higher)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage growth:&lt;/strong&gt; design for millions of short codes with room to grow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low latency:&lt;/strong&gt; keep both shortening and redirects fast (sub-100ms when possible)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  So where do we start?
&lt;/h2&gt;

&lt;p&gt;The best place to start is with the simplest possible implementation of the core functionality: shortening URLs and redirecting to them. From here, we can iteratively add features, optimizations, and infrastructure components, each time motivated by a real need that arises from the limitations of the previous version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before we dive into code, a quick note on approach (you should skip this if you don't care for me getting all philosophical)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you start simple and get something working, storage methods, scaling strategies, and database schemas follow naturally from the problems you encounter, not from upfront speculation.&lt;/p&gt;

&lt;p&gt;Over my career, I've met countless developers who approach this in the exact reverse order, and I used to be one of them: I'd start with the database model, design the perfect schema, plan the caching strategy, and then try to build the application around it.&lt;/p&gt;

&lt;p&gt;Starting with the database model, however, locks you in early. For starters it decides &lt;em&gt;that&lt;/em&gt; you'll be using a database, maybe even a particular brand. &lt;strong&gt;But an application should dictate storage, not the other way around&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now I build production applications differently: I build the simplest thing that works, and let the architecture emerge from real constraints. It's not just an approach for this blog series, it's how I work day-to-day.&lt;/p&gt;

&lt;p&gt;I know this isn't some kind of mind-blowing revelation, but if you've never tried it, I highly recommend it: it's allowed me to demonstrate and discuss with stakeholders early-on, and it's kept my pet projects from stalling out, because when you see something working quickly, you keep those sweet-sweet endorphins pumping instead of getting stuck in architecture decisions and losing interest.&lt;/p&gt;

&lt;p&gt;Alright, enough with the philosophical digression, let's finally see some code 😅&lt;/p&gt;

&lt;h2&gt;
  
  
  Flipr 0: let's go ephemeral!
&lt;/h2&gt;

&lt;p&gt;As promised, this first version is intentionally minimal, without any external dependencies such as databases or caching layers: pure business logic and a simple HTTP server. Everything lives in memory, which means it all disappears when the server restarts. Exactly how we like it for now :)&lt;/p&gt;

&lt;h3&gt;
  
  
  The stack
&lt;/h3&gt;

&lt;p&gt;Nothing fancy, just solid, battle-tested tools that get the job done!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; for adding some sanity ontop of JavaScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js&lt;/strong&gt; as the runtime&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Express&lt;/strong&gt; for the HTTP server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Winston&lt;/strong&gt; for structured logging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zod&lt;/strong&gt; for request validation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Business logic
&lt;/h3&gt;

&lt;p&gt;Let's start with the core: the &lt;code&gt;Shortener&lt;/code&gt; class, which is where all the URL shortening logic lives, completely independent of HTTP, databases, or any infrastructure concerns. By keeping this business logic separate from transport and storage, the code stays clean and testable. As a bonus, we could reuse it in other contexts later, like a CLI tool or a different web framework.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;validator&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;./validator&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;NUMBER_OF_CODE_GENERATOR_RETRIES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ShortRecord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CodeRestrictedError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CodeRestrictedError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FailedUrlRetrievalError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FailedUrlRetrievalError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShortCodeNotFoundError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ShortCodeNotFoundError&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="nx"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;ShortRecord&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generateShortCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &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;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;code&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;exists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;code&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;insert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ShortRecord&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;code&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;generateShortCodeWithRetry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateShortCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&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;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;NUMBER_OF_CODE_GENERATOR_RETRIES&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="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&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;code&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateShortCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;attempts&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to generate a unique short code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CodeBlockList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;reserved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;offensive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&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;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ShortenerConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;shortcodeLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;codeBlockList&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CodeBlockList&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Shortener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&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;ShortenerConfig&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;validate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validator&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;codeBlockList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reserved&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;codeBlockList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offensive&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;codeBlockList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;protected&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;private&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;shorten&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customCode&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ShortRecord&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customCode&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customCode&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CodeRestrictedError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Custom code is restricted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;newShortCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="nx"&gt;customCode&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="nf"&gt;generateShortCodeWithRetry&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shortcodeLength&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;test&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newShortCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newShortCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FailedUrlRetrievalError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to retrieve the shortened URL&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shortCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ShortRecord&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shortCode&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ShortCodeNotFoundError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Short code not found&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shortCode&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;h3&gt;
  
  
  Let's break this down
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Storage:&lt;/strong&gt; The &lt;code&gt;lookup&lt;/code&gt; object is our entire database. It's a simple key-value store mapping short codes to URLs. When the server restarts, everything disappears. Perfect for now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code Generation:&lt;/strong&gt; The &lt;code&gt;generateShortCode&lt;/code&gt; function creates random strings from a 62-character alphabet (a-z, A-Z, 0-9). With a default length of 7 characters, that gives us 62^7 = ~3.5 trillion possible codes. Plenty of headroom.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Collision Handling:&lt;/strong&gt; The &lt;code&gt;generateShortCodeWithRetry&lt;/code&gt; function tries up to 10 times to generate a valid code. "Valid" means it passes the blocklist check &lt;em&gt;and&lt;/em&gt; doesn't already exist in our lookup. If we can't find a valid code after 10 attempts, we throw an error. In practice, with trillions of possible codes and an empty database, collisions are very unlikely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blocklists:&lt;/strong&gt; the imported &lt;code&gt;validator&lt;/code&gt; function checks three lists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reserved:&lt;/strong&gt; things like &lt;code&gt;api&lt;/code&gt;, &lt;code&gt;health&lt;/code&gt;, &lt;code&gt;admin&lt;/code&gt; that can be used to claim authority and exploit unsuspecting users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offensive:&lt;/strong&gt; self-explanatory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protected:&lt;/strong&gt; codes we might want to use for special purposes later, or might be exploited by confusing people, such as names of well-known social media sites&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Custom codes:&lt;/strong&gt; users can optionally provide their own short code (like &lt;code&gt;flipr.io/mysite&lt;/code&gt;). We validate it the same way as generated codes. If it's restricted or already taken, we throw a &lt;code&gt;CodeRestrictedError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error types:&lt;/strong&gt; three custom error classes let the HTTP layer distinguish between different failure modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CodeRestrictedError&lt;/code&gt;: The custom code is blocked or taken (422 Unprocessable Entity)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ShortCodeNotFoundError&lt;/code&gt;: The code doesn't exist (404 Not Found)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FailedUrlRetrievalError&lt;/code&gt;: Something went wrong after insertion (500 Internal Server Error)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation keeps the business logic clean. The &lt;code&gt;Shortener&lt;/code&gt; class doesn't know anything about HTTP status codes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring it up with Express
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://expressjs.com/" rel="noopener noreferrer"&gt;Express server&lt;/a&gt; is straightforward. Here are the two main endpoints:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shortening a URL:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/shorten&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;URL shortening request&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;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;shortening_try&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;originalUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;customCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;custom_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;custom_code&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URLRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;shortener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shorten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;custom_code&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;shortUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;URL shortened successfully&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;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;shortening_success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;shortCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;short_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;short_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;shortUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;original_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;API shorten error:&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;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;shortening_error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;CodeRestrictedError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;422&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Custom code restricted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="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;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;FailedUrlRetrievalError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to retrieve the shortened URL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We validate the request body with &lt;a href="https://github.com/colinhacks/zod" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;, call &lt;code&gt;shortener.shorten()&lt;/code&gt;, and return the result. If the shortener throws one of our custom errors, we map it to the appropriate HTTP status code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resolving a short code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/:shortCode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Redirection request&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;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;redirect_try&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;shortCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shortCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;shortener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shortCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Redirection successful&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;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;redirect_success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;shortCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shortCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;destinationUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;302&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Redirect error:&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;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;redirect_error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;ShortCodeNotFoundError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;h1&amp;gt;Short URL not found&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="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the redirect endpoint. When someone visits &lt;code&gt;flipr.io/abc123&lt;/code&gt;, we look up the code and send a 302 redirect to the original URL. If the code doesn't exist, we return a 404.&lt;/p&gt;

&lt;p&gt;The rest of the Express setup is standard boilerplate: CORS headers, static file serving for a simple web UI, a health check endpoint, and &lt;a href="https://github.com/winstonjs/winston" rel="noopener noreferrer"&gt;Winston&lt;/a&gt; logging throughout. &lt;a href="https://github.com/edeckers/flipr-distributed-url-shortener" rel="noopener noreferrer"&gt;You can see the full server code in this GitHub repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it locally
&lt;/h2&gt;

&lt;p&gt;Want to try Flipr yourself? The code is open source and ready to run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clone and install:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/edeckers/flipr-distributed-url-shortener.git
&lt;span class="nb"&gt;cd &lt;/span&gt;flipr
git checkout part-1-ephemeral
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Start the server:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server will start on &lt;code&gt;http://localhost:8000&lt;/code&gt;. You'll see Winston logging output in your terminal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shorten a URL:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/api/shorten &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"url": "https://example.com/some/very/long/url"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll get back a response like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"short_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aB3xY9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"short_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8000/aB3xY9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"original_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/some/very/long/url"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Test the redirect:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Visit &lt;code&gt;http://localhost:8000/aB3xY9&lt;/code&gt; in your browser, or use curl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-L&lt;/span&gt; http://localhost:8000/aB3xY9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Try a custom code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/api/shorten &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"url": "https://example.com", "custom_code": "mysite"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have the web UI enabled, you can also visit &lt;code&gt;http://localhost:3000&lt;/code&gt; in your browser for a simple form interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;In the next post, we'll containerize Flipr and create basic Kubernetes configurations using Kustomize. We'll explore how to package the application for deployment and set up the foundation for running it in a distributed environment.&lt;/p&gt;

&lt;p&gt;The code evolves in branches on GitHub, so you can follow along commit by commit, deploy it yourself, or jump to any stage that interests you. This is designed as a learning resource for understanding distributed systems architecture, and it's fully open source under the MPL-2.0 license.&lt;/p&gt;

&lt;p&gt;If you spot issues, have suggestions, or want to contribute, open an issue or PR on the repo. I'm building this in public as both a learning exercise and a reference implementation for self-hosting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping it up
&lt;/h2&gt;

&lt;p&gt;We started with a simple question: what's the minimum viable URL shortener? Turns out, it's about 150 lines of TypeScript. This ephemeral version won't survive a restart, and it won't scale beyond a single instance, but that's fine and intended: we've proven the concept works, and we have a solid foundation to build on.&lt;/p&gt;

&lt;p&gt;Next part of this series, we'll dockerize the application and add a basic Kubernetes configuration. See you then!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Have you ever started a project by building the database schema first, only to realize later that your application logic didn't quite fit? Or do you prefer starting with the data model? I'd love to hear how you approach greenfield projects in the comments!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the Dutch language doesn't scare you, and you'd like to know more about what keeps me busy aside from writing these blog posts, &lt;a href="https://branie.it" rel="noopener noreferrer"&gt;check my company website branie.it&lt;/a&gt;! Maybe we can work together on something someday :)&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>kubernetes</category>
      <category>architecture</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Stopping Bad Actors: Inside 1Password’s Security Model</title>
      <dc:creator>Ely</dc:creator>
      <pubDate>Sat, 11 Oct 2025 13:16:23 +0000</pubDate>
      <link>https://dev.to/edeckers/stopping-bad-actors-inside-1passwords-security-model-14c</link>
      <guid>https://dev.to/edeckers/stopping-bad-actors-inside-1passwords-security-model-14c</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post is &lt;a href="https://medium.branie.it/how-building-useless-software-made-me-a-better-developer-98e9c5f6cbc4" rel="noopener noreferrer"&gt;part of a series on my pet projects&lt;/a&gt; that I’ve worked on over the years and I feel would be nice to showcase. So if you like this idea, make sure to &lt;a href="https://medium.branie.it/recreating-my-first-computer-an-atari-2600-emulator-7b72279a4afd" rel="noopener noreferrer"&gt;check out my Atari 2600 emulator from my previous post&lt;/a&gt;!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;strong&gt;I’ve been using password managers for many years, trusting them with literally everything: from my bank accounts to my inbox; my entire digital life actually. And since you’re reading this, you probably do too.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From the moment I started using a password manager, I wondered: how does this actually work? Why is this secure, &lt;em&gt;is&lt;/em&gt; this secure? I pieced together a pretty firm idea of how things must work under the hood. And as it turns out, I wasn’t too far off.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But there’s a difference between having a mental model and really understanding something. For me, the best way to bridge such a gap is to build it. So I created a &lt;a href="https://github.com/edeckers/lib1password-unofficial" rel="noopener noreferrer"&gt;fully 1Password-compatible TypeScript library&lt;/a&gt; and turned it into an &lt;a href="https://passwords.lgtm.it/" rel="noopener noreferrer"&gt;interactive explainer&lt;/a&gt;. I'm a web developer, not a cryptographer, which means security-wise it will leave something to be desired, but the implementation works with real 1Password data, which means I must have understood &lt;em&gt;something&lt;/em&gt; 😅&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let me walk you through the process, using the credentials of the one and only legendary actor and director Tommy Wiseau, an avid 1Password user and ambassador himself!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foq4af48s5upmpqk5851j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foq4af48s5upmpqk5851j.jpg" alt="Closeup of person gesturing 'ssh' with finger against mouth" width="800" height="512"&gt;&lt;/a&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@tinaflour" rel="noopener noreferrer"&gt;Kristina Flour&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/grayscale-photo-of-woman-doing-silent-hand-sign-BcjdbyKWquw" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Protecting Tommy Wiseau’s scripts and your passwords
&lt;/h2&gt;

&lt;p&gt;Imagine you’re &lt;em&gt;the&lt;/em&gt; &lt;strong&gt;Tommy Wiseau&lt;/strong&gt;: you’re between takes on your latest project, and you need to access your script notes. But here’s the thing: you can’t afford to have your scripts leaked. Your creative process is &lt;em&gt;unique&lt;/em&gt;. Your dialogue is &lt;em&gt;distinctive&lt;/em&gt;. One leak and the internet would have a field day.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tommy Wiseau famously starred in the &lt;a href="https://www.youtube.com/watch?v=7h7QG7W14qs" rel="noopener noreferrer"&gt;Stopping Bad Actors&lt;/a&gt; commercial for 1Password, check it out!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, naturally, you use a password manager, 1Password in particular. But how does 1Password actually protect Tommy’s precious scripts? How does it keep his passwords secure even from 1Password themselves? Let’s find out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lq7fapo1fiiaj1st4d5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lq7fapo1fiiaj1st4d5.gif" alt="Tommy Wiseau saying he can't elaborate about something, because it's confidential" width="640" height="320"&gt;&lt;/a&gt;Tommy knows what's up&lt;/p&gt;

&lt;h2&gt;
  
  
  The Journey: from curiosity to code
&lt;/h2&gt;

&lt;p&gt;Tommy's passwords are stored on 1Password's servers, but they claim they can't decrypt his vault even if they wanted to. How does that work? What's stopping a rogue employee from accessing his script for "The Room 2"? &lt;em&gt;(hey, one can dream)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I found my answer in 1Password's &lt;a href="https://1passwordstatic.com/files/security/1password-white-paper.pdf" rel="noopener noreferrer"&gt;Security Design white paper&lt;/a&gt;. It's an excellent read and it's pretty palatable, much more so than I expected beforehand. A recommended read for sure!&lt;/p&gt;

&lt;p&gt;But understanding theory is one thing. I wanted to implement it. Because for me, the best way to truly understand something is to build it. So I set out to create a &lt;strong&gt;TypeScript library&lt;/strong&gt; that implements 1Password's security model, &lt;strong&gt;which is capable of decrypting actual 1Password vault data&lt;/strong&gt;; if my implementation couldn't handle the real thing, how would I know I truly understood it?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A pleasant surprise: the Web Crypto API, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto" rel="noopener noreferrer"&gt;SubtleCrypto&lt;/a&gt; in particular, handles all the heavy lifting. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto#supported_algorithms" rel="noopener noreferrer"&gt;PBKDF2, AES-256-GCM, RSA-OAEP, it's all built into every modern browser&lt;/a&gt;. No external crypto libraries needed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  And so I verified it can process actual 1Password data
&lt;/h2&gt;

&lt;p&gt;To verify full compatibility, I created a test account, intercepted the encrypted data using &lt;a href="https://en.wikipedia.org/wiki/Burp_Suite" rel="noopener noreferrer"&gt;Burp Suite&lt;/a&gt; and the &lt;a href="https://github.com/1Password/burp-1password-session-analyzer" rel="noopener noreferrer"&gt;1Password Session Analyzer plugin&lt;/a&gt; to strip away the custom in-transit encryption layer on top of TLS, and &lt;a href="https://github.com/edeckers/lib1password-unofficial/blob/980b6c2115f4ae738a5a4e7753d1c78e45015ff5/spec/actualPasswordScenario.spec.ts" rel="noopener noreferrer"&gt;wrote a test that decrypts the data using my library&lt;/a&gt;. The test passes, because the implementation is byte-for-byte compatible with 1Password's production encryption. Pretty cool, right?&lt;/p&gt;

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

&lt;h2&gt;
  
  
  "But I don't want to look at code, just explain it to me"
&lt;/h2&gt;

&lt;p&gt;Fair enough, let's step through the process of what happens when Tommy unlocks his 1Password vault.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's important to realize that aside from the Account Password, &lt;strong&gt;each of the keys&lt;/strong&gt; in the steps below &lt;strong&gt;is randomly generated on your device&lt;/strong&gt; when you create your account; 1Password only ever sees their encrypted versions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;1. Account Password + Secret Key = Account Unlock Key (AUK)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tommy's password and his Secret Key are combined &lt;em&gt;on his device&lt;/em&gt; using PBKDF2 (650,000 iterations), plus some salts here and there, to create the AUK. Which is an intentionally expensive operation, happening once per session. It keeps brute-force attacks at bay, and his Account Password and Secret Key a secret from 1Password.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. AUK decrypts a keyset's Symmetric Key&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The AUK unlocks a fast &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt#aes-gcm" rel="noopener noreferrer"&gt;AES-256-GCM symmetric key&lt;/a&gt;. This key protects Tommy's &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt#rsa-oaep_2" rel="noopener noreferrer"&gt;RSA private key&lt;/a&gt;, while also making it easy to change the &lt;em&gt;Account Password&lt;/em&gt; or &lt;em&gt;Secret Key&lt;/em&gt;: only the &lt;em&gt;Keyset Access Key&lt;/em&gt; needs to be re-encrypted with the new &lt;em&gt;AUK&lt;/em&gt;. Everything downstream stays untouched. No need to re-encrypt your entire account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Symmetric Key decrypts RSA Private Key&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The symmetric key unlocks Tommy's RSA private key. Together with the public key, this keypair can decrypt his vault-specific keys without exposing his Account Password.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. RSA Keypair decrypts Vault Keys, which encrypt Tommy's Data&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each of Tommy's vaults has its own AES-256-GCM key, encrypted by his RSA public key. These vault keys finally decrypt his actual passwords and data, including those precious script notes.&lt;/p&gt;

&lt;p&gt;Here's what this looks like in the actual, albeit simplified, response from 1Password's servers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"uuid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rv4ge63poeyompbhty2efk5xxi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"encryptedBy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;mp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;auk&lt;/span&gt;&lt;span class="w"&gt;

 &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Layer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Symmetric&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(encrypted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;AUK)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"encSymKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PBES2g-HS256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xKj4pR0Ws-FyikpQGSxGk..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Encrypted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;symmetric&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;key&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"p2c"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;650000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;PBKDF&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;iterations&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

 &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

 &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Layer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;RSA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(encrypted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;symmetric&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;key)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"encPriKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UN1mx3kJh_Qeqdyt-7Jvq..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Encrypted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;key&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"kid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rv4ge63poeyompbhty2efk5xxi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

 &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

 &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

 &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Layer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;RSA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(unencrypted)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"pubKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RSA-OAEP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"n"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rwVd9EM4bEwLAI4QWzJhq..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

 &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

 &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why four layers?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F698jmkymaurg4xhqfj25.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F698jmkymaurg4xhqfj25.png" alt="A screenshot taken from the interactive explainer, showing a diagram of the 1Password keyset hierarchy" width="799" height="764"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PBKDF2 is slow by design&lt;/strong&gt; which adds protection against brute force&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Symmetric crypto is fast&lt;/strong&gt;, perfect for protecting the larger RSA private key&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RSA enables sharing&lt;/strong&gt;: vaults can be shared without sending Vault Passwords in plaintext or revealing your Account Password&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-vault keys&lt;/strong&gt; mean compromising one vault doesn't compromise others
-** Rolling Account Credentials is simple** only the Keyset Access Key needs to be re-encrypted with the new AUK&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6b87udwv72kjjd4nkdzy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6b87udwv72kjjd4nkdzy.gif" alt="Tommy Wiseau exclaiming 'Ha ha ha, What a story, Mark!'" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;
You right now, probably. My name is not Mark though



&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;Want to see this encryption chain in action? Head over to &lt;a href="https://passwords.lgtm.it/" rel="noopener noreferrer"&gt;my interactive 1Password explainer&lt;/a&gt;. You'll see each layer of the encryption process unfold step by step.&lt;/p&gt;

&lt;h2&gt;
  
  
  That's Encryption, Baby!
&lt;/h2&gt;

&lt;p&gt;Building this TypeScript implementation helped me understand what the 1Password Security Model looks like and why each layer exists. PBKDF2 slows down attackers and hides your password an secret key from 1Password. Symmetric crypto protects your private key efficiently. RSA enables secure sharing. Per-vault keys contain breaches.&lt;/p&gt;

&lt;p&gt;Seeing how these pieces fit together and getting it to work with real 1Password data, turned abstract concepts into some pretty thorough understanding.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7jvxx7c1ycp9bpegzls9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7jvxx7c1ycp9bpegzls9.png" alt="Still from " width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;
Still from "The Room" by Tommy Wiseau



&lt;p&gt;If you're curious about the implementation details or want to integrate 1Password vault decryption into your own projects, the full TypeScript library is available at &lt;a href="https://github.com/edeckers/lib1password-unofficial" rel="noopener noreferrer"&gt;https://github.com/edeckers/lib1password-unofficial&lt;/a&gt;. It's fully compatible with real 1Password data, just don't use it in production, and obviously don't trust a library by some internet stranger like myself with your actual credentials and secrets.&lt;/p&gt;

&lt;p&gt;For a deeper dive into 1Password's security model, particularly how it works in local client applications, &lt;a href="https://darthnull.org/inside-1password/" rel="noopener noreferrer"&gt;David Schuetz's deep dive series&lt;/a&gt; is an excellent resource. &lt;strong&gt;Fun fact:&lt;/strong&gt; I found his series because I encountered the string &lt;em&gt;"Obfuscation Does Not Provide Security But It Doesn't Hurt"&lt;/em&gt; (pretty funny, I think) in the source of the 1Password site, and Googling it, his site was the only hit. &lt;a href="https://darthnull.org/1pass-misc/" rel="noopener noreferrer"&gt;He touches on it briefly&lt;/a&gt; :)&lt;/p&gt;




&lt;p&gt;Did this post spark your curiosity about cryptography or password security? Are you working on similar projects, or do you have questions about the implementation? Let me know in the comments!&lt;/p&gt;

&lt;p&gt;If the Dutch language doesn't scare you, and you'd like to know more about what keeps me busy aside from my pet projects, &lt;a href="https://branie.it" rel="noopener noreferrer"&gt;check my company website branie.it&lt;/a&gt;! Maybe we can work together on something someday :)&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
