<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Colin McDonnell @colinhacks]]></title><description><![CDATA[Founder of Bagel Health, creator of Zod, poptimist, movie buff. Wanna go to Taco Bell?]]></description><link>https://colinhacks.com</link><image><url>https://colinhacks.com/icon.png</url><title>Colin McDonnell @colinhacks</title><link>https://colinhacks.com</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 29 Apr 2026 03:20:55 GMT</lastBuildDate><atom:link href="https://colinhacks.com/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Tue, 03 Mar 2020 04:00:00 GMT</pubDate><copyright><![CDATA[2020 Colin McDonnell]]></copyright><language><![CDATA[en]]></language><managingEditor><![CDATA[colin@colinhacks.com]]></managingEditor><webMaster><![CDATA[colin@colinhacks.com]]></webMaster><ttl>60</ttl><item><title><![CDATA[Introducing Zod Codecs]]></title><description><![CDATA[<p>Zod 4.1 introduced a new <code>z.codec()</code> API for defining bi-directional transformations in Zod.</p>
<h2 id="theproblemwithtransforms">The problem with transforms</h2>
<p>Zod's <code>.transform()</code> method is great for one-way data conversion:</p>
<pre><code class="ts language-ts">const stringToNumber = z.string().transform(val =&gt; parseFloat(val));
stringToNumber.parse("42"); // 42
</code></pre>
<p>But what if you need to go both ways? Say, you're storing dates as ISO strings in a database but want to work with <code>Date</code> objects in your app.</p>
<pre><code class="ts language-ts">const stringToDate = z.string().transform(str =&gt; new Date(str));
const dateToString = z.date().transform(date =&gt; date.toISOString());

// Two separate schemas, manually kept in sync
stringToDate.parse("2024-01-15T10:30:00.000Z"); // Date
dateToString.parse(new Date()); // "2024-01-15T10:30:00.000Z"
</code></pre>
<p>This works, but it's brittle. You need to keep track of two schemas and remember that they are intended as inverses. You need to manually verify that the output type of one matches the input type of the other. If you change one, you have to remember to update the other. </p>
<h2 id="introducingcodecs">Introducing codecs</h2>
<p>Codecs are a new Zod API for defining <em>bidirectional transformations</em> between two types. You specify an input schema, output schema, and transformation functions in both directions:</p>
<pre><code class="ts language-ts">const stringToDate = z.codec(
  z.iso.datetime(),  // input schema: ISO string
  z.date(),          // output schema: Date object
  {
    decode: isoString =&gt; new Date(isoString), // string → Date
    encode: date =&gt; date.toISOString(),       // Date → string
  }
);
</code></pre>
<p>You can process data in both directions using the new top-level <code>.decode()</code> and <code>.encode()</code> methods:</p>
<pre><code class="ts language-ts">stringToDate.decode("2024-01-15T10:30:00.000Z"); // Date
stringToDate.encode(new Date("2024-01-15"));     // "2024-01-15T00:00:00.000Z"
</code></pre>
<blockquote>
  <p><strong>Note</strong>&nbsp;— For bundle size reasons, these new methods have not added to Zod Mini schemas. Instead, this functionality is available via equivalent top-level functions. </p>
<pre><code class="ts language-ts">// equivalent at runtime
z.decode(stringToDate, "2024-01-15T10:30:00.000Z");
z.encode(stringToDate, new Date());
</code></pre>
</blockquote>
<p>This is particularly important when you are using Zod to <em>map data</em> back and forth between two different domains. One common use case is to convert data to/from a serializable format like JSON into a richer JavaScript representation (with <code>Date</code>, <code>bigint</code>, etc).</p>
<p><img src="/codecs/codecs-network-dark.svg" alt="Zod schemas shared" /></p>
<h3 id="async">Async</h3>
<p>The transformation functions can be <code>async</code>.</p>
<pre><code class="ts language-ts">const asyncCodec = z.codec(z.string(), z.number(), {
  decode: async str =&gt; Number(str),
  encode: async num =&gt; num.toString(),
});
</code></pre>
<p>The usual "safe" and "async" variants exist:</p>
<pre><code class="ts language-ts">syncCodec.encode("42");
syncCodec.safeEncode("42");
await asyncCodec.encodeAsync("42");
await asyncCodec.safeEncodeAsync("42");
</code></pre>
<h3 id="composability">Composability</h3>
<p>Codecs can be composed inside other schemas, just like any other schema. There are no special rules.</p>
<pre><code class="ts language-ts">const queryParams = z.object({
  before: stringToDate,
  after: stringToDate
})

queryParams.encode({
  before: new Date(),
  after: new Date()
});
// =&gt; { before: string, after: string }
</code></pre>
<h3 id="parsevsdecode"><code>.parse()</code> vs <code>.decode()</code></h3>
<p>Let's compare the existing <code>.parse()</code> APIs to <code>.decode()</code>. <em><code>.parse()</code> is equivalent to <code>.decode()</code> at runtime.</em></p>
<pre><code class="ts language-ts">// equivalent at runtime
stringToDate.parse("2024-01-15T10:30:00.000Z"); 
stringToDate.decode("2024-01-15T10:30:00.000Z");
</code></pre>
<p>Though they're identical at runtime, their type signatures differ in an important way. While <code>.parse()</code> accepts <code>unknown</code>, <code>decode</code> expects a <em>strongly-typed inputs</em>.</p>
<pre><code class="ts language-ts">stringToDate.parse(12345); 
// No TypeScript error but fails at runtime

stringToDate.decode(12345);
// ❌ TypeScript error: Argument of type 'number' is not assignable to parameter of type 'string'
</code></pre>
<p>Here's a diagram demonstrating the differences:</p>
<p><img src="/codecs/codecs-dark.png" alt="Codec directionality diagram" /></p>
<blockquote>
  <p>This is a highly requested feature unto itself.</p>
  <ul>
  <li><a href="https://github.com/colinhacks/zod/issues/3860">#3860</a> Add strongly typed parse function</li>
  <li><a href="https://github.com/colinhacks/zod/issues/1748">#1748</a> Typed input for parse methods</li>
  <li><a href="https://github.com/colinhacks/zod/issues/3978">#3978</a> Type-safe parsing with known input types</li>
  <li><a href="https://github.com/colinhacks/zod/issues/1892">#1892</a> Strongly typed decode function</li>
  </ul>
</blockquote>
<h2 id="howencodingworks">How encoding works</h2>
<p>Most Zod schemas in the universe don't perform any kind of transformation. Their inferred input and output types are identical. For these schemas, there is no difference between parsing/decoding and encoding.</p>
<pre><code class="ts language-ts">const mySchema = z.object({
  name: z.string()
});

// no difference
mySchema.parse({ name: "colinhacks" });
mySchema.decode({ name: "colinhacks" })
mySchema.encode({ name: "colinhacks" })
</code></pre>
<p>A small number of APIs cause the input and output types to diverge. In these scenarios, the runtime behavior of <code>.decode()</code>/<code>.encode()</code> also differ.</p>
<h3 id="codecs">Codecs</h3>
<p>This is an obvious one. During <code>.decode()</code>, the <code>decode</code> function runs. During <code>.encode()</code>, the <code>encode</code> function runs. Simple.</p>
<h3 id="transforms">Transforms ⚠️</h3>
<p>This is the #1 rule of <code>.encode()</code>: you can't use <code>.transform()</code>. That API is inherently unidirectional. If your schema contains any transforms, attempting an "encode" operation with it will throw a runtime error. You'll need to refactor to use <code>z.codec()</code>.</p>
<pre><code class="ts language-ts">const schema = z.string().transform(val =&gt; val.length);

schema.encode(5); 
// ❌ ZodEncodeError: Encountered unidirectional transform during encode
</code></pre>
<h3 id="pipes">Pipes</h3>
<blockquote>
  <p><strong>Note</strong> — Codecs are actually implemented as a subclass of <code>ZodPipe</code> augmented with "interstitial" transform logic.</p>
</blockquote>
<p>Pipes reverse their order during encoding, from <code>A → B</code> to <code>B → A</code>. That said, pipes are typically used in conjunction with transforms, so "vanilla" pipes are rarely useful in the context of encoding. Prefer <code>z.codec()</code> everywhere.</p>
<h3 id="refinements">Refinements</h3>
<p>All checks (<code>.refine()</code>, <code>.min()</code>, <code>.max()</code>, etc.) are still executed in both directions. </p>
<pre><code class="ts language-ts">const schema = stringToDate.refine((date) =&gt; date.getFullYear() &gt; 2000, "Must be this millenium");

schema.encode(new Date("2000-01-01"));
// =&gt; Date

schema.encode(new Date("1999-01-01"));
// =&gt; ❌ ZodError: [
//   {
//     "code": "custom",
//     "path": [],
//     "message": "Must be this millenium"
//   }
// ]
</code></pre>
<p>To avoid unexpected errors in your custom <code>.refine()</code> logic, Zod performs two "passes" during <code>.encode()</code>. The first pass ensures the input type conforms to the expected type (no <code>invalid_type</code> errors). If that passes, Zod performs the second pass which executes the refinement logic.</p>
<p>This approach means all parsing &amp; refinement logic runs in exactly the reverse order during encoding. Even "mutating refinements" like <code>z.string().trim()</code> or <code>z.string().toLowerCase()</code> work as expected. </p>
<pre><code class="ts language-ts">const schema = z.string().trim();

schema.decode("  hello  ");
// =&gt; "hello"

schema.encode("  hello  ");
// =&gt; "hello"
</code></pre>
<h3 id="defaultprefault">Default/prefault</h3>
<p>Default and prefault values are only applied in the forward direction. </p>
<pre><code class="ts language-ts">const withDefault = z.string().default("hello");

withDefault.decode(undefined); // "hello"
withDefault.encode(undefined); // ❌ ZodError
</code></pre>
<p>This is by design. When you add a default, the input becomes <code>string | undefined</code> but the output stays <code>string</code>. As such, <code>undefined</code> isn't considered a valid input to <code>.encode()</code>.</p>
<h3 id="catch">Catch</h3>
<p>Similarly, <code>.catch()</code> values are only applied in the forward direction.</p>
<h3 id="stringbool">Stringbool</h3>
<blockquote>
  <p><strong>Note</strong> — <a href="/api#stringbool">Stringbool</a> pre-dates the introduction of codecs in Zod. It has since been internally re-implemented as a codec. </p>
</blockquote>
<p>The <code>z.stringbool()</code> API converts string values (<code>"true"</code>, <code>"false"</code>, <code>"yes"</code>, <code>"no"</code>, etc.) into <code>boolean</code>. By default, it will convert <code>true</code> to <code>"true"</code> and <code>false</code> to <code>"false"</code> during <code>.encode()</code>..</p>
<pre><code class="ts language-ts">const stringbool = z.stringbool();

stringbool.decode("true");  // =&gt; true
stringbool.decode("false"); // =&gt; false

stringbool.encode(true);    // =&gt; "true"
stringbool.encode(false);   // =&gt; "false"
</code></pre>
<p>If you specify a custom set of <code>truthy</code> and <code>falsy</code> values, the <em>first element in the array</em> will be used instead.</p>
<pre><code class="ts language-ts">const stringbool = z.stringbool({ truthy: ["yes", "y"], falsy: ["no", "n"] });

stringbool.encode(true);    // =&gt; "yes"
stringbool.encode(false);   // =&gt; "no"
</code></pre>
<h2 id="officialcodecs">Official codecs</h2>
<p>Zod doesn't provide any predefined codecs out of the box. Instead, the docs provide some "canonical" codec implementations you can copy/paste into your projects as needed. These have all been tested internally. </p>
<ul>
<li><a href="https://zod.dev/codecs?id=stringtonumber"><code>stringToNumber</code></a></li>
<li><a href="https://zod.dev/codecs?id=stringtoint"><code>stringToInt</code></a></li>
<li><a href="https://zod.dev/codecs?id=stringtobigint"><code>stringToBigInt</code></a></li>
<li><a href="https://zod.dev/codecs?id=numbertobigint"><code>numberToBigInt</code></a></li>
<li><a href="https://zod.dev/codecs?id=isodatetimetodate"><code>isoDatetimeToDate</code></a></li>
<li><a href="https://zod.dev/codecs?id=epochsecondstodate"><code>epochSecondsToDate</code></a></li>
<li><a href="https://zod.dev/codecs?id=epochmillistodate"><code>epochMillisToDate</code></a></li>
<li><a href="https://zod.dev/codecs?id=jsoncodec"><code>jsonCodec</code></a></li>
<li><a href="https://zod.dev/codecs?id=utf8tobytes"><code>utf8ToBytes</code></a></li>
<li><a href="https://zod.dev/codecs?id=bytestoutf8"><code>bytesToUtf8</code></a></li>
<li><a href="https://zod.dev/codecs?id=base64tobytes"><code>base64ToBytes</code></a></li>
<li><a href="https://zod.dev/codecs?id=base64urltobytes"><code>base64urlToBytes</code></a></li>
<li><a href="https://zod.dev/codecs?id=hextobytes"><code>hexToBytes</code></a></li>
<li><a href="https://zod.dev/codecs?id=stringtourl"><code>stringToURL</code></a></li>
<li><a href="https://zod.dev/codecs?id=stringtohttpurl"><code>stringToHttpURL</code></a></li>
<li><a href="https://zod.dev/codecs?id=uricomponent"><code>uriComponent</code></a></li>
<li><a href="https://zod.dev/codecs?id=stringtoboolean"><code>stringToBoolean</code></a></li>
</ul>
<p>Some selected examples are below.</p>
<h3 id="stringtobigint"><code>stringToBigInt</code></h3>
<pre><code class="ts language-ts">const stringToBigInt = z.codec(z.string(), z.bigint(), {
  decode: str =&gt; BigInt(str),
  encode: bigint =&gt; bigint.toString(),
});

stringToBigInt.decode("12345");  // 12345n
stringToBigInt.encode(12345n);   // "12345"
</code></pre>
<h3 id="jsoncodec"><code>jsonCodec</code></h3>
<pre><code class="ts language-ts">const jsonCodec = z.codec(z.string(), z.json(), {
  decode: (jsonString, ctx) =&gt; {
    try {
      return JSON.parse(jsonString);
    } catch (err: any) {
      ctx.issues.push({
        code: "invalid_format",
        format: "json_string",
        input: jsonString,
        message: err.message,
      });
      return z.NEVER;
    }
  },
  encode: value =&gt; JSON.stringify(value),
});
</code></pre>
<p>You can pipe <code>jsonCodec</code> into other schemas for additional validation:</p>
<pre><code class="ts language-ts">const UserFromJson = jsonCodec.pipe(z.object({ 
  name: z.string(), 
  age: z.number() 
}));

UserFromJson.decode('{"name":"Alice","age":30}');  // { name: "Alice", age: 30 }
UserFromJson.encode({ name: "Bob", age: 25 });     // '{"name":"Bob","age":25}'
</code></pre>
<h3 id="base64tobytes"><code>base64ToBytes</code></h3>
<pre><code class="ts language-ts">const base64ToBytes = z.codec(z.base64(), z.instanceof(Uint8Array), {
  decode: base64String =&gt; z.core.util.base64ToUint8Array(base64String),
  encode: bytes =&gt; z.core.util.uint8ArrayToBase64(bytes),
});

base64ToBytes.decode("SGVsbG8=");  // Uint8Array([72, 101, 108, 108, 111])
base64ToBytes.encode(bytes);       // "SGVsbG8="
</code></pre>
<hr />
<p>For further reading, see the <a href="https://github.com/colinhacks/zod/releases/tag/v4.1.0">Zod 4.1 release notes</a> and the <a href="https://zod.dev/codecs">Codecs documentation page</a>.</p>]]></description><link>https://colinhacks.com/essays/introducing-zod-codecs</link><guid isPermaLink="false">/essays/introducing-zod-codecs</guid><category><![CDATA[Zod]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Validation]]></category><dc:creator><![CDATA[Colin McDonnell]]></dc:creator><pubDate>Sat, 23 Aug 2025 00:44:28 GMT</pubDate></item><item><title><![CDATA[Making AI resources auto-discoverable via package.json]]></title><description><![CDATA[<p>I propose the following convention for JavaScript libraries to make their AI resources auto-discoverable via package.json.</p>
<pre><code class="json language-json">{
  // package.json
  "llms": "https://docs.cool-library.com/llms.txt",
  "llmsFull": "https://docs.cool-library.com/llms-full.txt",
  "mcpServer": "https://mcp.cool-library.com",
}
</code></pre>
<p>I've implemented this in <a href="https://github.com/colinhacks/zod/blob/main/packages/zod/package.json">Zod</a> and encourage my fellow library authors to do the same. Moreover, I encourage agentic IDEs/CLIs to implement first-party support for this convention. </p>
<p>If you add support to your library or agent, please fill out <a href="https://docs.google.com/forms/d/e/1FAIpQLSew8sx6KxjwHkBGlgbUWHNEzSAt9GM8ryJ-X39d0gEltGT_-g/viewform?usp=header">this form</a>.</p>
<h2 id="details">Details</h2>
<ol>
<li>The <code>llms</code> field is a URL pointing to an <a href="https://llmstxt.org/">llms.txt</a> file. If you aren't familiar, this is a proposed convention for documentation sites to provide an AI-friendly "sitemap" to AI agents. It's a simple spec: basically just a bunch of categorized Markdown links.</li>
<li>The <code>llmsFull</code> field is a URL pointing to an <code>llms-full.txt</code> file containing the full documentation for the library. Typically this is a concatenation of all the <code>.md</code>/<code>.mdx</code> documentation pages in the docs.</li>
<li>The <code>mcpServer</code> field is a URL pointing to an MCP server. For a typical library, this server would offer a simple Q&amp;A tool that is capable of answering questions about the library's API/usage. This can use RAG over the library's docs, GitHub issues, Discord server, etc.</li>
</ol>
<h2 id="doagentsalreadylookatthesefields">Do agents already look at these fields?</h2>
<p>No. This is a proposed convention. As more libraries implement this, there is more incentive for agents (IDEs, CLIs, etc) to implement first-party support. On the flip side of the coin, if any agentic CLI/IDE implements support, there is more incentive for libraries to implement this convention. There's a chicken-and-egg problem.</p>
<p>Fortunately, you can probably get your agent of choice to support this with some custom instructions in your <code>AGENTS.md</code> file.</p>
<pre><code class="md language-md">&lt;!-- AGENTS.md --&gt;
For each package in my package.json, read its package.json and look for the `llms`, `llmsFull`, and `mcpServer` fields. If found, use these resources to work with that library:

- `llms`: Use this URL to fetch the llms.txt file, which contains categorized Markdown links to the library's documentation. Use this as a sitemap to understand what documentation is available and how it's organized.

- `llmsFull`: Use this URL to fetch the llms-full.txt file, which contains the complete documentation for the library. Use this for comprehensive understanding of the library's API, features, and usage patterns.

- `mcpServer`: Connect to this MCP server URL to access specialized tools for answering questions about the library. Use this server's Q&amp;A capabilities when I need specific help with the library's API, troubleshooting, or best practices. If possible, prompt the user to add this server to their MCP server registry.
</code></pre>
<h2 id="whynotagentsmd">Why not <code>AGENTS.md</code>?</h2>
<p>This doesn't solve the same problem.</p>
<p><code>AGENTS.md</code> is for telling agents how to work <em>inside</em> your codebase. It's like a contributor's guide: how to run tests, run builds, lint, update configs, etc. It gives the agent best practices for modifying a codebase.</p>
<p>This is a qualitatively different problem that a library trying to advertise it's own functionality to an agent. The codebase is already installed in <code>node_modules</code>; agents shouldn't be modifying it. </p>
<p>An agent doesn't care about the package's internals, it only cares about the public API. Like any developer, it doesn't want to read your code to learn how to do stuff—that's hard, time-consuming, and expensive. Having some well-known <code>package.json</code> fields that have an established semantic meaning is a cleaner solution with higher signal-to-noise ratio.</p>
<hr />
<p>If you maintain a library that implements this convention, fill out <a href="https://docs.google.com/forms/d/e/1FAIpQLSew8sx6KxjwHkBGlgbUWHNEzSAt9GM8ryJ-X39d0gEltGT_-g/viewform?usp=header">this form</a>.</p>
<!-- 


1. this is a proposed convention. no agents read this fields currently. to my maintainer friends: I encourage you to do the same. if enough libraries do this, the agent CLIs/IDEs will eventually implement support

2. "why not AGENTS.md?" this doesn't solve the same problem. agents.md is used in a codebase to tell agents the structure of your codebase, how to run common tasks (test running, linting, etc). it's a "contributor's guide" for agents. this is a qualitative different problem than a library trying to advertise its own functionality to an agent. the structure of the codebase doesn't matter. agents don't care. they just want to know the consumable API: how to use the library.

3. "why would libraries have an MCP server anyway?" in Zod's case, all of its docs and issues are all RAG-ified by Inkeep and queryable via the MCP server. 
https://x.com/colinhacks/status/1950109840560238644 -->]]></description><link>https://colinhacks.com/essays/ai-autodiscovery-in-package-json</link><guid isPermaLink="false">/essays/ai-autodiscovery-package.json</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[AI]]></category><category><![CDATA[Agents]]></category><dc:creator><![CDATA[Colin McDonnell]]></dc:creator><pubDate>Wed, 30 Jul 2025 01:23:04 GMT</pubDate></item><item><title><![CDATA[Live types in a TypeScript monorepo]]></title><description><![CDATA[<blockquote>
  <p>EDIT: A previous version of this post recommended <code>publishConfig</code>, operating under the mistaken belief that it could be used to override <code>"exports"</code> during <code>npm publish</code>. As it turns out, <code>npm</code> only uses <code>"publishConfig"</code> to override certain <code>.npmrc</code> fields like <code>registry</code> and <code>tag</code>, whereas <code>pnpm</code> has expanded its use to override package metadata like <code>"main"</code>, <code>"types"</code>, and <code>"exports"</code>. There are a number of reasons you may not wish to strongly couple your deployment logic to <code>pnpm</code> (detailed in the <code>publishConfig</code> section below). My updated recommendation is to use a custom export condition plus <code>customConditions</code> in <code>tsconfig.json</code>.</p>
  <p>Jump into the code: https://github.com/colinhacks/live-typescript-monorepo</p>
</blockquote>
<p>In development, your TypeScript code should feel "alive". When you update your code in one file, the effects of that change should propagate to all files that import it instantaneously, with no build step. This is true even for monorepos, where you may not be importing things from a file, but from a local package.</p>
<pre><code class="diff language-diff">- import { Fish } from "../pkg-a/index";
+ import { Fish } from "pkg-a"
</code></pre>
<p>This is vital to TypeScript's value proposition.</p>
<p>This post explains a few strategies you can use to make your TypeScript monorepo feel more alive. Refer to the corresponding repo where you can play around with the code for each strategy: https://github.com/colinhacks/live-typescript-monorepo</p>
<p>The repo contains three subdirectories, each containing a monorepo with the following file structure:</p>
<pre><code>.
├── package.json
├── packages
│&amp;nbsp;&amp;nbsp; ├── pkg-a
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── README.md
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── index.ts
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── package.json
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; └── tsconfig.json
│&amp;nbsp;&amp;nbsp; └── pkg-b
│&amp;nbsp;&amp;nbsp;     ├── README.md
│&amp;nbsp;&amp;nbsp;     ├── index.ts
│&amp;nbsp;&amp;nbsp;     ├── package.json
│&amp;nbsp;&amp;nbsp;     └── tsconfig.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── tsconfig.base.json
└── tsconfig.json
</code></pre>
<p>This is a pnpm monorepo (<code>pnpm-workspace.yaml</code>) with two packages, <code>pkg-a</code> and <code>pkg-b</code>. <strong><code>pkg-b</code> has a dependency on <code>pkg-a</code></strong>. Each package has a <code>tsconfig.json</code> that extends a <code>tsconfig.base.json</code> in the root of the monorepo.</p>
<p>Here is a quick rundown of the solutions. Don't worry if there are terms you're not familiar with, everything is explained in the breakdown.</p>
<ol>
<li>Use project references (<code>"references"</code> in <code>tsconfig.json</code>)</li>
<li>Use <code>publishConfig</code> in <code>package.json</code> to specify <code>.ts</code> file in development and <code>.js</code> file in production. <em>Requires <code>pnpm</code>.</em></li>
<li>Configure <code>compilerOptions.paths</code> in <code>tsconfig.json</code> to override resolution for local package names.</li>
<li><strong>Recommended</strong> Define a custom conditional export condition in <code>package.json#exports</code>.</li>
</ol>
<p>Note that I'm explaining all the solutions I found for the sake of education, but the recommended solution is #5 (custom export conditions) so feel free to jump ahead if you're just looking for a solution!</p>
<h2 id="aprimerruntimevsstatic">A primer: runtime vs. static</h2>
<p>The fundamental annoyance here is this: Node.js has an algorithm for <em>module resolution</em>. When it sees an import from a <em>bare specifier</em> like <code>"pkg-a"</code>, it scans up the directory tree checking for a directory called <code>"pkg-a"</code> in each <code>node_modules</code> folder it encounters. Once it finds <code>"pkg-a"</code>, it reads the <code>package.json</code> inside and uses the <code>main</code> and <code>exports</code> to figure out how to resolve the bare specifier to a file on disk.</p>
<p>The TypeScript server does <em>almost</em> the same thing, though it uses the <code>"types"</code> field (or the <code>"types"</code> export condition in <code>"exports"</code>) to find the <em>declaration file</em> corresponding to a particular package. There are also <em>lots</em> of ways to hack TypeScript's module resolution algorithm. (We'll get to that in a bit.)</p>
<p>But in development, we want things to behave differently. We want the TypeScript server to look at our "raw" <code>.ts</code> files when resolving imports to other local packages in our monorepo, <em>not</em> the compiled declaration files. Similarly, when we execute our local code (say, when running tests) we want it to "run" our TypeScript source code. You shouldn't need to rebuild your project before running tests.</p>
<p>So we need a way to hijack module resolution both statically (for TypeScript) and at runtime (for Node.js or tools like Vitest). We also need to make sure <em>both</em> of these things are hijacked in a way that they agree with each other. If TypeScript is looking at our <code>src/index.ts</code> files but Node.js is still importing <code>lib/index.js</code>, the types may not reflect the runtime behavior of the code. You may have seen this referred to as <em>static-runtime disagreement</em>.</p>
<p>Okay, enough background. Let's get into the details.</p>
<h2 id="1projectreferences">1. Project references</h2>
<blockquote>
  <p>The code for this is under the <code>project-references</code> subdirectory.
  Project references are a TypeScript feature that make it easier to split a large TypeScript codebase into chunks that are typechecked separately by the TypeScript server. This can be a huge performance win for large codebases.</p>
</blockquote>
<p>In our monorepo, <code>pkg-b</code> has a dependency on <code>pkg-a</code>. So in our <code>packages/pkg-b/tsconfig.json</code>, we can add a reference to <code>pkg-a</code> like so:</p>
<pre><code class="json language-json">{
  "references": [{"path": "../pkg-a"}]
}
</code></pre>
<p>Unfortunately the <code>"references"</code> field is <a href="https://twitter.com/andhaveaniceday/status/1798613232208527826">not inherited</a> when you use <code>"extends"</code>, so you have to declare it in every package's <code>tsconfig.json</code>. If your monorepo packages have a lot of interconnection, this can get unwieldy fast.</p>
<p>You may also notice that <code>"references"</code> will end up <a href="https://twitter.com/andhaveaniceday/status/1798717319990112609">mirroring the <code>"dependencies"</code></a> list in <code>package.json</code>. These will need to be kept in sync to work as expected. There are tools (<a href="https://nx.dev/"><code>nx</code></a>) that try to automate this, but the need to run a command to re-generate configs starts to feel like a "build step" in itself…and that's what we're trying to avoid.</p>
<blockquote>
  <p>There are <a href="https://github.com/microsoft/TypeScript/issues/25376">long-gestating efforts</a> to make this more ergonomic, but there seems to be little movement on this front.</p>
</blockquote>
<p>The final nail in the coffin is the difficulty of incorporating <code>"references"</code> into runtime module resolution. It's purely a TypeScript feature, and no tools incorporate this into their module resolution. So in all likelihood, you'd need to use one of the approaches below <em>in conjunction</em> with project references to achieve true "live types" with runtime <code>.ts</code> resolution.</p>
<blockquote>
  <p>There are absolutely scenarios where project references are indispensable. If you have a large enough monorepo, project references may become necessary to avoid re-typechecking the entire codebase when you make a change! But for non-giant projects, they aren't necessary and introduce too much complexity and potential footguns.</p>
</blockquote>
<h2 id="2publishconfiginpackagejson">2. <code>"publishConfig"</code> in <code>package.json</code></h2>
<blockquote>
  <p>This approach requires <code>pnpm</code> to work! The <code>publishConfig</code> field behaves very differently between <code>npm publish</code> and <code>pnpm publish</code>.</p>
</blockquote>
<p>One obvious solution is just to have each package's <code>package.json</code> point to our <code>.ts</code> files in <code>package.json</code>.</p>
<pre><code class="json language-json">{
  "name": "pkg-a",
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "exports": {
    ".": {
      "import": "./src/index.ts",
      "require": "./src/index.ts",
      "types": "./src/index.ts"
    },
    "./package.json": "./package.json"
  }
}
</code></pre>
<p>This introduces an obvious and immediate problem. We can't publish our raw <code>.ts</code> files to npm without breaking most tools. They need to be properly transpiled to JavaScript first. When we run <code>npm publish</code>, we need these fields to point to the appropriate <code>lib/index.js</code> and <code>lib/index.d.ts</code> files.</p>
<p>Many people have written custom build scripts that will duplicate <code>package.json</code> and rewrite these fields before publishing. Fortunately the good folks at <code>pnpm</code> have given us a better option: <code>publishConfig</code>.</p>
<pre><code class="json language-json">{
  "name": "pkg-a",

  // development config
  "exports": "./src/index.ts",

  // production config
  "publishConfig": {
    "main": "./lib/index.js",
    "types": "./lib/index.d.ts",
    "exports": {
      ".": {
        "import": "./lib/index.js",
        "require": "./lib/index.js",
        "types": "./lib/index.d.ts"
      },
      "./package.json": "./package.json"
    }
  }
}
</code></pre>
<p>When you run <code>pnpm publish</code>, pnpm will read the <code>publishConfig</code> field in <code>package.json</code> and use those values to override the top-level values for <code>"main"</code>, <code>"exports"</code>, and <code>"types"</code>.</p>
<blockquote>
  <p>While <code>npm</code> supports a field called <code>publishConfig</code>, it only lets you set <code>npm config</code> settings like <code>registry</code> and <code>tag</code>. Sad.</p>
</blockquote>
<p>In essence, our top-level <code>exports</code>, <code>main</code>, <code>types</code>, etc. now <em>only apply in development</em>, so we can point these to raw <code>.ts</code> files! TypeScript will happily parse these files, as will any modern bundler, framework, or a tool like <code>tsx</code>.</p>
<p>For simplicity, I've only set one field: <code>exports</code>, and I'm setting it to a simple string value. This has some benefits:</p>
<ol>
<li>It's clean! You don't need to redundantly declare a big <code>"exports"</code> object in both the top-level <code>package.json</code> and <code>"publishConfig"</code>. (Though you still can if you rely on subpath imports.)</li>
<li>It's safe! By specifying <code>"exports"</code> as a single string, no subpath imports are allowed. That level of strictness is good. It means you can't use subpath imports internally that would be illegal using the configuration specified in <code>publishConfig</code>.</li>
<li>It just works at runtime. Tools like <code>tsx</code>, <code>esbuild</code>, Vite, etc. can all happily resolve this import using just the single <code>exports</code> key. You don't need <code>"main"</code> unless you're using Node.js 10 or earlier in your development environment.</li>
<li>Similarly, TypeScript is happy. I lied a bit earlier…it doesn't need a special <code>"types"</code> field. It's more than happy to fall back to <code>"exports"</code> and pull type signatures out of a regular <code>.ts</code> source file.</li>
</ol>
<p>The big downside is the reliance on <code>pnpm</code>. That means if you ever run <code>npm publish</code> by accident, you'll accidentally publish a package that isn't runnable by Node.js 💀 You also can't rely on popular GitHub Actions like <code>JS-DevTools/npm-publish</code> since those use <code>npm</code>. This obstacle is surmountable with some tweaks to CI and diligence around publishing, but it is an important gotcha.</p>
<h2 id="3pathsintsconfigjson">3. <code>"paths"</code> in <code>tsconfig.json</code></h2>
<p>TypeScript provides another way to "hijack" module resolution: the <code>compilerOptions.paths</code>.</p>
<pre><code class="json language-json">{
  "compilerOptions": {
    "paths": {
      "pkg-a": ["./packages/pkg-a/src/index.ts"],
      "pkg-b": ["./packages/pkg-b/src/index.ts"]
    }
  }
}
</code></pre>
<p>The <code>compilerOptions.paths</code> option overrides TypeScript's normal module resolution. Any import that matches a key in paths will be resolved to the corresponding file. (If you specify multiple paths, TypeScript will use the first one that exists on your file system.)</p>
<p>This should be added to the <code>tsconfig.json</code> for every package in your monorepo. You can avoid redundancy by having all of your package <code>tsconfig</code>s extend a shared <code>tsconfig.base.json</code>.</p>
<p>Remember, we also need to incorporate this into our runtime module resolution.</p>
<p>The popular <code>tsx</code> tool by <a href="https://twitter.com/privatenumbr"><code>@privatenumbr</code></a> <a href="https://tsx.is/faq#how-does-tsx-compare-to-ts-node">does this automatically</a>. This is the best way to run a TypeScript file with Node.js.</p>
<pre><code class="sh language-sh">$ npm install -g tsx
$ tsx src/index.ts
</code></pre>
<p>You can also use <code>tsx</code> in conjunction with Node.js's <code>--import</code> flag.</p>
<pre><code class="sh language-sh">$ npm install tsx
$ node --import tsx src/index.ts
</code></pre>
<p>In the Vite/Vitest ecosystem, there is a popular plugin to do the same called <a href="https://www.npmjs.com/package/vite-tsconfig-paths"><code>vite-tsconfig-paths</code></a>.</p>
<p>This solution can be a bit fiddly, and requires some diligence in how you configure per-package <code>tsconfigs</code>. It should also be noted that the TypeScript team <a href="https://twitter.com/andhaveaniceday/status/1770466153615577306">kinda hates</a> <code>tsconfig.paths</code>, and there are a lot of ways to shoot yourself in the foot with it.</p>
<h2 id="4livedevmodeintshy">4. <code>liveDev</code> mode in <code>tshy</code></h2>
<p>The <a href="https://github.com/isaacs/tshy"><code>tshy</code></a> (<strong>T</strong>ype<strong>S</strong>cript <strong>Hy</strong>bridizer) tool is an opinionated tool by the creator of <code>npm</code> that makes it simple to build ESM and CommonJS packages from your TypeScript source code.</p>
<p>It recently added support for a <code>liveDev</code> mode that will hardlink your TypeScript source code into <code>./dist/esm</code> and <code>./dist/commonjs</code> directories. To set this up, install <code>tshy</code> into <code>devDependencies</code>.</p>
<pre><code class="sh language-sh">$ pnpm add tshy --dev
</code></pre>
<p>Add the following <code>"tshy"</code> config to your <code>package.json</code>:</p>
<pre><code class="json language-json">{
  "tshy": {
    "liveDev": true
  }
}
</code></pre>
<p>Then run <code>tshy</code> in your package directory.</p>
<pre><code class="sh language-sh">$ npx tshy
</code></pre>
<blockquote>
  <p>For simplicity, add <code>"tshy"</code> as the <code>"build"</code> script in each of your workspaces. Then you can run this script in each workspace with one command from the root of your workspace. (The exact command depends on your package manager.)</p>
</blockquote>
<p>This lets VS Code discover your live TypeScript source code without any additional <code>package.json</code> configuration! And tools like TypeScript and Vitest will be able to resolve your workspace imports to the <code>.ts</code> files with no additional configuration at all!</p>
<p>The downside is that this requires running <code>tshy</code> in each of your workspaces packages, which starts to feel like a build step.</p>
<p>The upside is that you only need to do this once! Once your <code>src</code> files are hard-linked into <code>dist</code>, you can edit them like normal and those changes are automatically reflected in <code>dist</code>. (This is how hard links work.)</p>
<blockquote>
  <p>The downside is that you'll need to re-run <code>tshy</code> each time you add a new TypeScript file (due to how hard links work…). Running <code>tshy --watch</code> can mitigate the "new file" problem, but for the purposes of this repo, I'm avoiding any solutions that require a file system watcher.</p>
</blockquote>
<h2 id="5customconditionsinexports">5. Custom conditions in <code>"exports"</code></h2>
<p>This lets you specify your TypeScript files in <code>package.json#exports</code> under a custom <em><a href="https://nodejs.org/api/packages.html#conditional-exports">export condition</a></em> of your choosing.</p>
<p>The mostly widely-utilized export conditions are <code>import</code> (for specifying ESM code), <code>require</code> (for specifying CJS code), and <code>types</code> (for specifying type definitions).</p>
<pre><code class="jsonc language-jsonc">// package.json
{
  "name": "pkg-a",
  "exports": {
    "*": {
      "import": "./lib/index.js",
      "require": "./lib/index.cjs"
    }
  }
}
</code></pre>
<p>But you're allowed to use any string you like as a custom export condition! Export conditions are intended as an open-ended mechanism for users to specify import entrypoints for specific runtimes, bundlers, or other tooling. For instance, <code>"deno"</code>, <code>"bun"</code>, and <code>"workerd"</code> (Cloudflare Workers) all support custom conditions so libraries can ship build that are specific to those runtimes.</p>
<p>Here's how a custom condition might look in your <code>package.json</code>. There's nothing special about the string <code>"@colinhacks/zod"</code> here! It could be anything.</p>
<pre><code class="jsonc language-jsonc">// package.json
{
  "name": "pkg-a",
  "exports": {
    "*": {
      "import": {
        "@colinhacks/source": "./src/index.ts", // must be first, order matters!
        "default": "./lib/index.js",
        "types": "./lib/index.d.ts"
      },
      "require": {
        "@colinhacks/source": "./src/index.ts",
        "default": "./lib/index.cjs",
        "types": "./lib/index.d.ts"
      }
    }
  }
}
</code></pre>
<blockquote>
  <p>You should put your custom condition <em>first</em>. Order matters! It's important that your custom condition is the first one in the list (even before <code>"types"</code>).</p>
</blockquote>
<p>By default, neither TypeScript nor Node.js pays attention to this new <code>"@colinhacks/source"</code> export condition. You need to tell them to incorporate it into their respective module resolution algorithms.</p>
<p>To tell TypeScript, add <code>"@colinhacks/source"</code> to the <code>customConditions</code> field in <code>tsconfig.json</code> for all packages in your monorepo. (You can avoid redundancy by having all of your package <code>tsconfig</code>s extend a shared <code>tsconfig.base.json</code>.)</p>
<pre><code class="json language-json">{
  "compilerOptions": {
    // include this in the tsconfig for all packages
    "customConditions": ["@colinhacks/source"]
  }
}
</code></pre>
<p>Node.js can accept custom conditions via the <code>--conditions</code> flag.</p>
<pre><code class="sh language-sh">node --import=tsx --conditions=@colinhacks/source ./src/index.ts
</code></pre>
<p>In the Vite/Vitest ecosystem, this can be configured with <code>resolve.conditions</code> setting.</p>
<pre><code class="js language-js">// vite.config.json
export default {
  resolve: {
    conditions: ['@colinhacks/source'],
  },
};
</code></pre>
<h3 id="anoteonconditionnaming">A note on condition naming</h3>
<p>I chose <code>"@colinhacks/source"</code> as the condition name for the sake of uniqueness. You want to pick a condition name that will only be defined in your workspace packages <em>only</em>. If you choose something generic like <code>"source"</code>, you may have dependencies with the same condition defined in their <code>package.json</code>. In this case, VS Code will resolve imports of that package using its <code>"source"</code> files, instead of using it's pre-compiled <code>.d.ts</code> files from <code>"types"</code>. This can hurt performance of the TypeScript server and slow down your editor.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Overall, my recommendation is #5: custom export conditions.</p>
<ul>
<li>It's clean, easy to configure, and works well with modern tooling.</li>
<li>It doesn't require you to use <code>pnpm</code>, which is a non-starter for many projects.</li>
<li>You don't need to worry about keeping runtime and TypeScript configurations in sync: you just set <code>"exports"</code> once using your custom condition, then tell your other tools (including TypeScript itself) to pay attention to that condition.</li>
</ul>
<p>As usual the "comments section" for this post is on Twitter. Feel free to make comments or ask questions in the replies to this tweet:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">new blog post 👇 it breaks down a few approaches to configuring &quot;live types&quot; in TypeScript monorepos. you should never need to run `build` while developing!<br><br>1. tsconfig paths<br>2. custom export conditions<br>3. publishConfig (*my recommended solution)<a href="https://t.co/sf6F3CfcsQ">https://t.co/sf6F3CfcsQ</a></p>&mdash; Colin McDonnell (@colinhacks) <a href="https://twitter.com/colinhacks/status/1796595123595378822?ref_src=twsrc%5Etfw">May 31, 2024</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>Happy monorepo hacking!</p>]]></description><link>https://colinhacks.com/essays/live-types-typescript-monorepo</link><guid isPermaLink="false">/essays/live-types-typescript-monorepo</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[Monorepos]]></category><dc:creator><![CDATA[Colin McDonnell]]></dc:creator><pubDate>Thu, 30 May 2024 21:59:04 GMT</pubDate></item><item><title><![CDATA[An email regex for reasonable people]]></title><description><![CDATA[<p>I maintain Zod, which is a schema library. Zod lets people declare an email schema like this:</p>
<pre><code class="ts language-ts">import {z} from 'zod';

z.string().email();
</code></pre>
<p>A lot of people think every <em>technically valid</em> email address should pass validation by that schema. I <a href="https://github.com/colinhacks/zod/issues/639#issuecomment-917223045">used to think that</a> too.</p>
<p>Over the years I've merged several PRs to make the email regex more "technically correct". Most recently, I merged a PR that adds support for <em>IPv6 addresses</em> as the domain part of an email. They look like this and I hate them:</p>
<pre><code>jonny@[ipv6:7e95:0559:10f2:21e9:9dab:7309:c116:ca3b]
</code></pre>
<p>Turns out that PR also broke plain old subdomains: <code>user@subdomain.domain.com</code>. That means Zod currently fails to parse my <em>mom's current email address</em> but Jonny up there can parse his freakish IPv6 email.</p>
<p>This caused a spiritual crisis and made me re-evaluate my whole stance on what <code>z.string().email()</code> should do. Zod's users are mostly engineers who are building apps. When you're building an app, you want to make sure your users are providing normal-ass email addresses. So that's what <code>z.string().email()</code> is going do.</p>
<p>So I rewrote the regex from scratch to be simple and reasonable. Here's what I came up with:</p>
<pre><code class="ts language-ts">/^(?!\.)(?!.*\.\.)([a-z0-9_'+\-\.]*)[a-z0-9_+\-]@([a-z0-9][a-z0-9\-]*\.)+[a-z]{2,}$/i;
</code></pre>
<p>Let's break that down in plain English:</p>
<p><strong>The username</strong> (AKA "local part")</p>
<ul>
<li>can use letters, numbers, and these special characters: <code>_'+-.</code></li>
<li>can't have two dots in a row</li>
<li>can't start or end with a dot</li>
</ul>
<p><strong>The domain</strong></p>
<ul>
<li>must consist of at least 2 dot-separated segments consisting of letters, numbers, and hyphens</li>
<li>can't have two dots in a row</li>
<li>must end with a "TLD" that's 2+ letters</li>
</ul>
<p>It is <em>not</em> trying to be <a href="https://datatracker.ietf.org/doc/html/rfc5322">RFC 5322</a> compliant. It's not going to check if the TLD is real. And it's not going to implement any of this craziness:</p>
<ul>
<li>No "printable" characters: <code>wtf-!#$%&amp;'*/=?^_{|}~@mail.com</code></li>
<li>No quoted local parts: <code>"zod is cool"@mail.com</code></li>
<li>No comments: <code>regexiscool(kinda)@mail.com</code></li>
<li>No IPv4: <code>billie@[1.2.3.4]</code></li>
<li>No IPv6: <code>jonny@[ipv6:7e95:0559:10f2:21e9:9dab:7309:c116:ca3b]</code></li>
<li>No emoji: <code>👎@mail.com</code></li>
</ul>
<p>But for reasonable people, it'll do.</p>]]></description><link>https://colinhacks.com/essays/reasonable-email-regex</link><guid isPermaLink="false">/essays/email-regex-reasonable</guid><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Colin McDonnell]]></dc:creator><pubDate>Wed, 08 Mar 2023 06:13:36 GMT</pubDate></item><item><title><![CDATA[From README to documentation site in 10 minutes]]></title><description><![CDATA[<blockquote>
  <p>Spoiler alert: GitHub Pages and GitHub actions are not involved.</p>
</blockquote>
<p>I recently set out to build a proper documentation site for <a href="https://github.com/colinhacks/zod">Zod</a> with a short and eminently reasonable list of goals:</p>
<ol>
<li>The site should be generated from the README. Zod's <code>README.md</code> is the "source of truth".</li>
<li>The site should automatically update whenever the <code>master</code> branch is updated.</li>
<li>I didn't want to worry about building or maintaining a complex build workflow in GitHub Actions, if possible.</li>
</ol>
<!-- 3. The entire site should be a single page. This is a personal preference; I don't like having to click through a complex site to find the information I'm looking for. I'd rather have everything on one long page, so I can use browser-native `Cmd+F` search to find what I need.
4. Relatedly, I also wanted a persistent sidebar that would auto-scroll you to a particular section when clicked.
5. The design should be better than GitHub's: a more spacious layout, better typography, and a [content width](https://baymard.com/blog/line-length-readability) optimized for readability. -->
<p>Despite the eminent reasonability, I had a hard time finding a simple solution. After a lot of research and false starts, I've found what I think is the lowest-effort, highest-reward way to publish a documentation site for your GitHub-hosted project.</p>
<p>Let's jump in.</p>
<h2 id="1addthisindexhtmltoyourrepo">1. Add this <code>index.html</code> to your repo</h2>
<p>Create a file called <code>index.html</code> in your project root and paste the following contents into it.</p>
<blockquote>
  <p>Replace all occurrences of <code>user/repo</code> with your project's name. And maybe write up a slightly more detailed <code>meta</code> description :)</p>
</blockquote>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;title&gt;user/repo&lt;/title&gt;
    &lt;meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /&gt;
    &lt;meta name="description" content="user/repo is cool!" /&gt;
    &lt;meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" /&gt;
    &lt;link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify/lib/themes/vue.css" /&gt;
    &lt;style&gt;
      .markdown-section { max-width: 700px; }
    &lt;/style&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;nav
      style="display: flex; flex-direction: row; align-items: center; justify-content: space-between;"
    &gt;
      &lt;!-- &lt;ul&gt;&lt;li href="/link"&gt;Link 1&lt;/li&gt;&lt;/ul&gt; --&gt;
    &lt;/nav&gt;
    &lt;div id="app"&gt;&lt;/div&gt;
    &lt;script&gt;
      window.$docsify = {
        subMaxLevel: 1,
        maxLevel: 3,
        auto2top: true,
        repo: 'user/repo',
        routerMode: 'history',
        // homepage: "README.md"
      };
    &lt;/script&gt;
    &lt;script src="//cdn.jsdelivr.net/npm/docsify@4.12.2/lib/docsify.min.js"&gt;&lt;/script&gt;
    &lt;script src="//cdn.jsdelivr.net/npm/prismjs@1.28.0/prism.min.js"&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Those 1150 bytes are doing a lot, but most importantly it's loading <a href="https://docsify.js.org/">Docsify</a>. Docsify is a documentation generator. You may recognize it from its goofy looking gradient cover pages with big pill-shaped buttons.</p>
<p><img src="/docsify.png" alt="Docsify sample homepage" /></p>
<blockquote>
  <p>I'm not a fan of these cover pages, so we won't be using one here. All in all, though, I think Docsify is extraordinarily well designed.</p>
</blockquote>
<p>It also differs from some other options like Docusaurus and Gitbook in that it's a <em>fully client-side</em> documentation generator. Your Markdown files don't get pre-compiled to HTML ahead of time; instead, Docsify fetches it asynchronously, parses the contents in-browser, and injects the rendered HTML into the page.</p>
<p><em>"The horror! Multiple round-trip requests before the first meaningful paint!?"</em> Turns out, it's not a big issue. Modern globe-spanning ultra-fast CDNs for static content, this still feels near-instant. Google agrees; the <a href="https://zod.dev">Zod site</a> gets a 98 Performance score on Lighthouse.</p>
<p><img src="/lighthouse.png" alt="98 performance on Lighthouse" /></p>
<h2 id="2runlocally">2. Run locally</h2>
<p>There's no build step required with Docsify. You could deploy your <code>index.html</code> and a <code>README.md</code> to any static site hosting service and it would work. We're going to be using <a href="https://www.netlify.com/">Netlify</a> to deploy this site, but before we get to that, let's see how the site is looking. We can use Netlify's CLI to start a simple static site server.</p>
<pre><code class="bash language-bash">$ npx netlify dev
◈ Netlify Dev ◈
   ┌─────────────────────────────────────────────────┐
   │                                                 │
   │   ◈ Server now ready on http://localhost:8888   │
   │                                                 │
   └─────────────────────────────────────────────────┘
</code></pre>
<p>Open <code>http://localhost:8888</code> in your browser, and you should see a rendered version of your <code>README.md</code>!</p>
<blockquote>
  <p>If <code>netlify dev</code> detects that you're using a framework, this may not work. In that case you'll need to disable framework detection and tell Netlify to act as a simple static file host.</p>
<pre><code class="sh language-sh">npx netlify dev --framework "#static"
</code></pre>
  <p>If your primary Markdown file isn't called <code>README.md</code>, you can specify a different name with the <code>homepage</code> setting in the Docsify configuration.</p>
<pre><code class="js language-js">window.$docsify = {
  subMaxLevel: 1,
  maxLevel: 3,
  auto2top: true,
  repo: 'user/repo',
  routerMode: 'history',
  homepage: 'home.md', // &lt;-- add this
};
</code></pre>
</blockquote>
<h2 id="3configurerewrites">3. Configure rewrites</h2>
<p>Docsify using client-side routing to determine which Markdown file to load and render. In other words, it's a pure single-page application; <code>index.html</code> loads Docsify, which then assumes control over your browsers URL bar, similar to React Router or other client-side routers.</p>
<p>If your project/site contains several Markdown files, you may encounter a strange behavior. Consider the following file structure:</p>
<pre><code>├── index.html
├── README.md
└── CONTRIBUTING.md
</code></pre>
<p>If you start the development server and type <code>localhost:8888/CONTRIBUTING</code> into the URL bar, you'll see a 404. That's because there's no HTML file called <code>CONTRIBUTING.html</code>. We need all incoming requests to render <code>index.html</code>! Then Docsify will read the URL and load the appropriate Markdown file.</p>
<p>We can achieve this with a Netlify feature called <a href="https://docs.netlify.com/routing/redirects/">rewrites</a>. (All static file hosts have an equivalent feature.) Create a file called <code>_redirects</code> with no extension. (With Netlify, both redirects and rewrites are declared in <code>_redirects</code>.)</p>
<pre><code>$ touch _redirects
</code></pre>
<p>Then paste in the following:</p>
<pre><code>/*            /             200
</code></pre>
<p>This tells Netlify that all incoming requests (<code>/*</code>) should render the homepage (<code>/</code>). However, unlike a <em>redirect</em>, rewrites do not change the URL. Basically Netlify will sneakily render <code>index.html</code> under the hood, without telling the browser.</p>
<blockquote>
  <p>Another solution is to use a <em>hash router</em>. In fact, this is the default behavior for Docsify. Unfortunately, it makes the URL messy; the URL would look like <code>domain.com/#/CONTRIBUTING</code> instead of simply <code>domain.com/CONTRIBUTING</code>. I prefer clean URLs so I chose the rewrites approach.</p>
</blockquote>
<p>Now when we run <code>npx netlify dev</code>, we can open <code>localhost:8888/CONTRIBUTING</code> directly without seeing a 404.</p>
<h2 id="4deployment">4. Deployment</h2>
<p>Firebase used to be my go-to for static file hosting, but these days Netlify leads the pack on developer experience. It only takes a couple minutes to set up Github-linked static site hosting.</p>
<p>First, let's push our new files to <code>master</code>.</p>
<pre><code class="sh language-sh">$ git add .
$ git commit -am "Add docs site"
$ git push origin master
</code></pre>
<p>Then configure Netlify.</p>
<ol>
<li>Log in or create an account at <a href="https://www.netlify.com/">netlify.com</a>.</li>
<li>From the dashboard, click <code>Add new site &gt; Import an existing project</code>.</li>
<li>Authenticate with GitHub (or GitLab or Bitbucket) and select your repository.</li>
</ol>
<p>When prompted for a <code>"Branch to deploy"</code> select <code>master</code> (the default) unless your primary branch has a different name.</p>
<p>Under "Build settings" you can simply leave everything blank:</p>
<ul>
<li><code>"Base directory"</code> — leave blank (defaults to the project root)</li>
<li><code>"Build command"</code> — leave blank (no build step necessary!)</li>
<li><code>"Publish directory"</code> — leave blank (defaults to the project root)</li>
</ul>
<p><img src="/netlify_build.png" alt="Netlify configuration settings" /></p>
<p>There is virtually no configuration required, because we're simply hosting static files from the root directory of our <code>master</code> branch. Couldn't be easier!</p>
<blockquote>
  <p>Note that we're deploying the entire contents of our repository to Netlify. You could write a build command to <a href="https://answers.netlify.com/t/can-i-exclude-certain-files-like-readme-md/15416">delete all unwanted files</a> but I didn't bother. The contents of my repo are already public on the internet, so…🤷‍♂️</p>
</blockquote>
<p>Proceed with the deployment. Once Netlify finishes deploying, Netlify will provide a <code>*.netlify.app</code> subdomain for your site.</p>
<h2 id="5finaltouches">5. Final touches</h2>
<p>There's plenty more to do before your site is truly ready.</p>
<ul>
<li>Connect a custom domain</li>
<li>Add a <code>robots.txt</code> to your root directory</li>
<li>Add links to the top <code>&lt;nav&gt;</code> in <code>index.html</code></li>
<li>Add <a href="https://ahrefs.com/blog/open-graph-meta-tags/">Open Graph</a> meta tags</li>
<li>Add <a href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup">Twitter</a> meta tags and a banner image</li>
<li>Generate a favicon (<a href="https://realfavicongenerator.net/">RealFaviconGenerator</a> is great)</li>
</ul>
<p>But I'll leave that as an exercise for the reader.</p>]]></description><link>https://colinhacks.com/essays/docs-the-smart-way</link><guid isPermaLink="false">/essays/docs-the-smart-way</guid><dc:creator><![CDATA[Colin McDonnell]]></dc:creator><pubDate>Fri, 13 May 2022 03:46:51 GMT</pubDate></item><item><title><![CDATA[Building an end-to-end typesafe API — without GraphQL]]></title><description><![CDATA[<p>In the mid-2010s, the pendulum of web development swung massively in the direction of increased type safety.</p>
<p>First, we all switched back to relational DBs after recovering from an embarrassingly long collective delusion that NoSQL was cool. Then TypeScript started its meteoric rise after years of quiet development. Shortly thereafter GraphQL was announced and quickly became the de facto way to implement typesafe APIs.</p>
<blockquote>
  <p>TLDR: I published a new library called tRPC that makes it easy to build end-to-end typesafe APIs without code generation, by leveraging the power of modern TypeScript. To jump straight to the tRPC walkthrough, <a href="#trpc">click here</a>.</p>
</blockquote>
<h3 id="graphqlstypescriptproblem">GraphQL's TypeScript problem</h3>
<p>GraphQL's biggest competitive advantage is that it's a <em>spec</em>, not a library. It's designed to be universal and language-agnostic. If you use different languages for your client and server, then by all means stick with GraphQL! It remains the best language-agnostic way to build typesafe data layers.</p>
<p>But the vast majority of projects that use GraphQL are JavaScript-based. The <code>graphql</code> NPM module has <a href="https://www.npmjs.com/package/graphql">5M downloads/week</a> vs <a href="https://pypistats.org/packages/graphene">~300k</a>) for the <code>graphene</code> Python module and <a href="https://bestgems.org/gems/graphql">~230k</a> for the <code>graphql</code> Rubygem. It's not a perfect metric, but the signal is clear.</p>
<p>And a growing fraction of JavsScript projects are TypeScript-based. As TypeScript approaches near-universal adoption in the JS ecosystem, that means <strong>most</strong> GraphQL-based projects will soon be TypeScript-based (if they aren't already).</p>
<p>Here's the thing: most people use GraphQL as a massively over-engineered way to share the <strong>type signature</strong> of their API with your client. What if you could import that type signature <em>directly into your client</em> using plain ole <code>import</code> syntax? Well…you can.</p>
<h2 id="ademonstrativeexample">A demonstrative example</h2>
<p>We're going to build the world's simplest typesafe API—no codegen or GraphQL required. There's only one endpoint, with the following type signature:</p>
<p><code>getUser(id: string) =&gt; { id: string, name: string }</code></p>
<p>For the sake of simplicity, the API is implemented as an Express router. It should be easy to tell what's going on even if you aren't familiar with it.</p>
<h3 id="1definegetuserfunction">#1 Define <code>getUser</code> function</h3>
<pre><code class="ts language-ts">// server/routes.ts
export const getUser = async (id: string) =&gt; {
  return { id, name: 'Bilbo' };
};
</code></pre>
<h3 id="2implementasimplerpcendpoint">#2 Implement a simple RPC endpoint</h3>
<p>We set up a special <code>/rpc</code> endpoint that accepts POST requests with a payload of type <code>{ endpoint: string; arguments: any[] }</code>. The <code>endpoint</code> property indicates the function to call (in our case, the only option is <code>"getUser"</code>). We take the <code>arguments</code> array, pass it into the appropriate function, and <code>res.send</code> the results.</p>
<pre><code class="ts language-ts">// server/router.ts
import express from 'express';
import * as routes from './routes';

type Payload = { endpoint: string; arguments: any[] };

const app = express();
app.post('/rpc', async (req, res) =&gt; {
  const payload: Payload = req.body;
  if (payload.endpoint === 'getUser') {
    const user = await routes.getUser(...payload.args);
    return res.status(200).send(user);
  }

  return res.status(404).send('Endpoint not found');
});
</code></pre>
<blockquote>
  <p><strong>A totally skippable aside about RPC</strong></p>
  <p>With a REST APIs, you use different URLs (<code>/users</code>, <code>/user/:id</code>) and HTTP request types (e.g GET, POST, etc) to access different functionality. For instance <code>GET /users</code> will fetch all users, <code>GET /user/abc123</code> will fetch a single user, and <code>POST /user</code> will create a new user.</p>
  <p>RPC strips all of that away. Endpoints are just named functions that accept some arguments and return a value. The RPC ethos is to make API calls look and feel like calling a function (albeit one that's defined on a remote server).</p>
  <p>GraphQL is an RPC protocol that's been augmented with a schema language and a mechanism for client-side field selection.</p>
</blockquote>
<h3 id="3importthetypesignatureofgetuserintotheclient">#3 Import the type signature of <code>getUser</code> into the client</h3>
<p>This is where the magic happens. We <strong>directly import</strong> the type signature of our API into out client. Yes, it really is this easy.</p>
<pre><code class="tsx language-tsx">// client/index.tsx
import type { getUser } from '../server/routes.ts';
</code></pre>
<p>Note that this works no matter where <code>routes.ts</code> lives in your file system! It could be in a different Git repo; it could be on your external hard drive. As long as the TypeScript file exists on your computer, you can import it from any other TypeScript file.</p>
<p><strong><em>"But wait, I can't just import stuff from a different repo!"</em></strong></p>
<p>This is often true…when you're importing code. For instance, Create React App (CRA) prohibits you from importing anything from outside of your <code>src</code> directory.</p>
<p>But that doesn't apply here. We're using <code>import type</code> syntax, which is a <strong>purely static construct</strong>. That is, it can only import <em>type signatures</em>, but not data, code, or any runtime construct. For most build systems powered by Babel (including CRA), <code>import type</code> statements (and all type annotations) just get stripped out before the bundling step.
If you're using Webpack with Babel, Parcel, or Rollup, this Just Works™. Don't believe me? Play around with this <a href="https://github.com/colinhacks/import-type-cra">sample repo</a> where you can see this approach working just fine in an unejected CRA project.</p>
<p>You can use <code>import type</code> to import a type from any <code>.ts</code> or <code>.tsx</code> file anywhere on your filesystem. Period. This will work even if your client and server are in different repositories in different parts of your filesystem.</p>
<blockquote>
  <p>Of course, if multiple developers are working on the same codebase, you'll need to standardize the relative locations of client and server repos, so relative imports work on everyone's machines. You can either establish a convention ("make sure the <code>server</code> and <code>client</code> repos are in the same folder") or use something like Git Submodules.</p>
</blockquote>
<!-- **_"But wait, Create React App doesn't let me import anything outside my `src` folder!"_**

That's true...when you're importing code. But `import type` is purely a **TypeScript construct**. It's ignored by CRA's build system.

Don't believe me? Check out this [sample repo](https://github.com/colinhacks/import-type-cra) so you can see this working just fine in an unejected CRA project.

- If you're using Next.js, you can do this out of the box! It's one of Next's best features
- If you're using `create-react-app`, you may be under the impression this is impossible; after all CRA doesn't allow any imports from outside of `src`. And that's true...when you're importing _code_. But all we
- If you're
  Since our client and server are both written in TypeScript, we can import the **type definition** of `getUser` directly: -->
<p><strong><em>"But wait, isn't importing stuff from the server into the client a security vulnerability?!"</em></strong></p>
<p>Yes—when you're importing code. But not here! Remember, we're using the <code>import type</code> syntax. It was introduced in early 2020 (<a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export">TypeScript 3.8</a>) and it comes with a very important guarantee:</p>
<p><img src="/import-type.png" alt="import type never leaves remnants at runtime" /></p>
<p>All <code>import type</code> declarations are removed <strong>completely</strong> when you compile your TypeScript code into a JavaScript bundle, no matter what. So <em>yes</em> — you can safely use <code>import type</code> without worrying about accidentally importing sensitive data from your server.</p>
<!-- **_"But wait, won't this interfere with TypeScript's `rootDir` inference algorithm?!"_**

Wow, you're a nerd. And yes, that's a possibility if—for some reason—you're
using `tsc` to compile your TypeScript frontend. But chances are you're using a
bundler like Webpack, Rollup, Parcel, Vite, Snowpack, or whatever bundler-du-jour comes out five seconds after I published this blog post. All these bundlers strip TypeScript annotations out of your code (including `import type`) before the bundling step. -->
<h3 id="4makeanapicall">#4 Make an API call</h3>
<p>Now that we've imported the type signature of <code>getUser</code>, how do we make strongly typed API call? Answer: a bit of TypeScript magic. (Don't worry, you don't need to understand how this works! Just know that it's possible!)</p>
<pre><code class="tsx language-tsx">// client/index.tsx
import type { getUser } from '../server/routes.ts';

type API = {
  getUser: getUser;
  // add additional routes here...
};

const query = &lt;Endpoint extends keyof API&gt;(
  endpoint: Endpoint,
  ...args: Parameters&lt;API[Endpoint]&gt;
): ReturnType&lt;API[Endpoint]&gt; =&gt; {
  return fetch('http://localhost:3000/api/rpc', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ endpoint, arguments: args }),
  }).then((response) =&gt; response.json()) as any;
};
</code></pre>
<p>We now have a <strong>fully typesafe</strong> <code>query</code> function, which you use like this:</p>
<pre><code class="ts language-ts">const user = await query('getUser', 'user_abc73bd39ef');
// =&gt; Promise&lt;{ id: "user_abc73bd39ef", name: "Bilbo" }&gt;
</code></pre>
<p>It's a function called <code>query</code>. The first argument is the <em>name</em> of the
endpoint; the second is the input of the function. (As implemented each
RPC endpoint can only accept a single input.)</p>
<p>TypeScript validates that the endpoint name is valid…</p>
<p><img src="/trpc-invalid-endpoint.png" alt="invalid endpoint name error" /></p>
<p>…typechecks the arguments…</p>
<p><img src="/trpc-invalid-arg.png" alt="invalid arguments" /></p>
<p>…and strongly types the result.</p>
<p><img src="/trpc-typed-payload.png" alt="typed payload" /></p>
<p>And if you update the definition of <code>getUser</code> on your server, the new type signature <strong>automatically</strong> propagates to your client at the speed of TypeScript, no code generation required. The developer experience is unreal.</p>
<p>By using a next-generation ORM to fetch data in your endpoints, you get most of the value of GraphQL — end-to-end typesafety, easy nested fetching, field selection, etc — in a TypeScript-native way.</p>
<!--
For the sake of completeness, lets finish building the homepage:
<pre><code class="tsx language-tsx">import { useEffect, useState } from 'react';
import { getUser } from './api';

const query = /* defined above */;

const IndexPage = () =&gt; {
  const [user, setUser] = useState&lt;{ id: string; name: string } | null&gt;(null);

  useEffect(() =&gt; {
    query('getUser', 'user_12378192874').then(setUser);
  }, []);

  if (!user) return &lt;div&gt;Loading...&lt;/div&gt;;

  return (
    &lt;div&gt;
      &lt;p&gt;ID: {user.id}&lt;/p&gt;
      &lt;p&gt;Name: {user.name}&lt;/p&gt;
    &lt;/div&gt;
  );
};

export default IndexPage;
</code></pre> -->
<!-- Voila! A fully typesafe application, without a drop of GraphQL. -->
<!-- I start playing around with these ideas in my own projects in mid-2020. Eventually I distilled this approach into a library called tRPC that makes it  -->
<p>If you want, you can use this approach exactly as described; all the code above is available as a Next.js project in <a href="https://github.com/colinhacks/typesafe-api-no-graphql">this repo</a>. But there's a better way!</p>
<p><a name="trpc"></a></p>
<h2 id="introducingtrpc">Introducing tRPC!</h2>
<p>Around 6 months ago, I distilled these ideas into a proof-of-concept library called tRPC. Since then, the inimitable <a href="https://twitter.com/alexdotjs">@alexdotjs</a> has turned it into a full-fledged library. Lets see how to implement our <code>getUser</code> example with tRPC. Spoiler alert: it's mindblowingly easy.</p>
<blockquote>
  <p>If you want to jump straight to the README, check it out at <a href="github.com/trpc/trpc">github.com/trpc/trpc</a> — and leave a star if you're feeling generous!</p>
</blockquote>
<h3 id="createarouter">Create a router</h3>
<pre><code class="ts language-ts">// server/index.ts
import * as trpc from '@trpc/server';

const appRouter = trpc.router();
export type AppRouter = typeof appRouter;
</code></pre>
<h3 id="addaqueryendpoint">Add a query endpoint</h3>
<p>Let's implement <code>getUser</code>.</p>
<pre><code class="ts language-ts">// server/index.ts
import * as trpc from '@trpc/server';

export const appRouter = trpc.router().query('getUser', {
  input: String,
  async resolve(req) {
    req.input; // string
    return { id: req.input, name: 'Bilbo' };
  },
});
</code></pre>
<p>A few things to note:</p>
<ul>
<li><p>You add "endpoints" by calling the <code>.query</code> method. The first argument is a <em>procedure name</em>. The second is an object containing two properties: <code>input</code> and <code>resolve</code>.</p></li>
<li><p>The <code>input</code> is a function that <strong>validates the input</strong>. For simplicity, all tRPC procedures accept a <em>single argument</em>. The type signature of this argument is inferred from the return type of the <code>input</code> function.</p>
<p>I recommend defining your inputs using a schema definition library (I hear <a href="https://github.com/colinhacks/zod">Zod</a> is pretty cool 🙃). Far too few developers rigorously validate incoming API payloads; this is a huge security risk and source of bugs. So tRPC has validation baked-in from the start.</p>
<p>For simplicity, I'm just using the built-in JavaScript <code>String</code> constructor, which accepts any value and converts it into a string.</p></li>
<li><p>The endpoint logic is defined in the <code>resolve</code> function. A "pseudorequest" is passed into it, which contains <code>input</code> (automatically typed as <code>string</code> in this case) and <code>ctx</code> (more on that later). tRPC <strong>infers the return type</strong> of your resolver. So make sure the returned value is statically typed! We recommend using a typesafe ORM like Prisma or TypeORM that automatically type the results of database operations; otherwise you'll need to write TypeScript types manually.</p></li>
</ul>
<h3 id="whataboutmutations">What about mutations?</h3>
<p>Like GraphQL, tRPC makes a distinction between queries and mutations. Add mutations to your router with the <code>mutation</code> method:</p>
<pre><code class="ts language-ts">// server/index.ts
import * as trpc from '@trpc/server';
import { z } from 'zod';
export const appRouter = trpc
  .router()
  .query('getUser', {
    input: String,
    async resolve(req) {
      req.input; // string
      return { id: req.input, name: 'Bilbo' };
    },
  })
  .mutation('createUser', {
    input: z.object({ name: z.string() }),
    async resolve(req) {
      return await prisma.user.create({
        data: req.input,
      });
    },
  });
</code></pre>
<p>The syntax for the <code>mutation</code> is <strong>identical</strong> to <code>query</code>. tRPC only makes a distinction because <code>query</code> payloads are cached differently than <code>mutation</code> payloads.</p>
<h3 id="chainablemethods">Chainable methods</h3>
<p>Important note: when you add multiple endpoints to a router, you <strong>must chain the calls</strong> to <code>query()</code> and <code>mutation()</code>! This lets tRPC infer the type signature of your whole API. If you're familiar with Express you may be tempted to do this:</p>
<pre><code class="ts language-ts">export const appRouter = trpc.router();

// ❌ DON'T DO THIS!! ❌

appRouter.query('getUser', {
  input: String,
  async resolve(req) {
    req.input; // string
    return { id: req.input, name: 'Bilbo' };
  },
});
</code></pre>
<p>This doesn't work at all. All router methods are <code>immutable</code>—they return an entirely new instance of <code>TRPCRouter</code>, instead of <em>modifying</em> the current instance. So you <em>must chain</em> these methods.</p>
<h3 id="mergingrouters">Merging routers</h3>
<p>At some point, you'll want to split up your tRPC router into multiple files. If all methods must be chained together, how is this possible?</p>
<p>tRPC provides an easy way to <strong>merge routers</strong>. I recommend creating a root <code>appRouter</code> and several "child routers" that you merge into it.</p>
<pre><code class="ts language-ts">import { userRouter } from './routers/user';
import { postRouter } from './routers/post';

export const appRouter = trpc
  .createRouter()
  .merge('users.', userRouter) // prefix userRouter endpoints with "users."
  .merge('posts.', postRouter); // prefix postRouter endpoints with "posts."
</code></pre>
<p>All procedure names will be prefixed with the string literal you pass as the first argument of <code>.merge</code>. So a <code>createUser</code> mutation on <code>userRouter</code> will be re-named to <code>users.createUser</code>. This is made possible by the incredible <em>string literal templates</em> feature introduced in <a href="https://devblogs.microsoft.com/typescript/announcing-typescript-4-1/#template-literal-types">TypeScript 4.1</a>!</p>
<h3 id="handingincomingrequests">Handing incoming requests</h3>
<p>Now lets use our router to handle some actual HTTP requests. tRPC ships "adapters" that easily let you generate Express middleware or Next.js API routes from your tRPC router.</p>
<h4 id="nextjsintegration">Next.js integration</h4>
<pre><code class="ts language-ts">// pages/api/trpc/[trpc].ts
import { createNextApiHandler } from '@trpc/server/adapters/next';

const appRouter = /* ... */;

export default createNextApiHandler({
  router: appRouter,
  createContext: (opts) =&gt; {
    return { bearer: opts.req.headers.authorization };
  },
});
</code></pre>
<h4 id="expressintegration">Express integration</h4>
<p>If you're using Express, do this instead:</p>
<pre><code class="ts language-ts">import { createExpressMiddleware } from '@trpc/server/adapters/express';

const appRouter = /* ... */;

const app = express();

app.use(
  '/trpc',
  createExpressMiddleware({
    router: AppRouter,
    createContext: () =&gt; null, // no context
  })
);

app.listen(80);
</code></pre>
<h4 id="koahapinestjs">Koa, Hapi, Nest.js</h4>
<p>Coming soon (and PRs welcome)!</p>
<h3 id="requestcontext">Request context</h3>
<p>All server adapters accept <code>createContext</code> function, where you can generate a <code>ctx</code> object from the "raw" HTTP request and response objects (available as <code>opts.req</code> and <code>opts.res</code>). This <code>ctx</code> is available in all your resolvers.</p>
<h3 id="clientsideusage">Client-side usage</h3>
<p>So what about the client side? The <code>@trpc/client</code> module generates a typesafe "tRPC client"—analagous to the <code>query</code> method we implemented in our toy example. You can import the the type signature of your <strong>entire API</strong> at once with <code>import type { AppRouter } from '...'</code>. Then pass it into <code>createTRPCClient</code>, along with the URL of your API:</p>
<pre><code class="ts language-ts">// pages/index.ts
import { createTRPCClient } from '@trpc/client';
import type { AppRouter } from './api/[trpc]';

const trpcClient = createTRPCClient&lt;AppRouter&gt;({
  url: 'http://localhost:3000/api',
});

trpcClient.query('getUser', 'user_1238823498');
// =&gt; Promise&lt;{ id: string; name: string }&gt;
</code></pre>
<h4 id="reactintegration">React integration</h4>
<p>If you use React, you can use the <code>@trpc/react</code> package to convert your tRPC client into React hooks powered by <a href="https://react-query.tanstack.com/">React Query</a>. This gets you a ton of cool features like request invalidation, SSR, and cursor-based pagination! 🚀</p>
<pre><code class="ts language-ts">import { createReactQueryHooks } from '@trpc/react';

// path to your backend server where your AppRouter is defined
import type { AppRouter } from '../server/trpc';

export const trpc = createReactQueryHooks&lt;AppRouter&gt;();
</code></pre>
<h4 id="vueandsvelte">Vue and Svelte</h4>
<p>Get in touch if you'd like to build dedicated bindings for those frameworks! Create an issue on <a href="https://github.com/trpc/trpc">the GitHub repo</a>.</p>
<h2 id="wrappingup">Wrapping up</h2>
<p>I've been using various iterations of tRPC in production for nearly a year, and it truly feels like living in the future. I'm immensely proud of what tRPC has become. Checkout the docs (and leave a star! 🤩) on <a href="https://github.com/trpc/trpc">the GitHub repo</a>.</p>
<p>There are a lot of other features I could highlight, but that'll do for now. Huge shoutout to <a href="https://twitter.com/alexdotjs">@alexdotjs</a>, who picked up a fledgling proof-of-concept four months ago and carried it to the finish line!</p>]]></description><link>https://colinhacks.com/essays/painless-typesafety</link><guid isPermaLink="false">/essays/painless-typesafety</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[GraphQL]]></category><category><![CDATA[tRPC]]></category><dc:creator><![CDATA[Colin McDonnell]]></dc:creator><pubDate>Sun, 13 Jun 2021 14:41:14 GMT</pubDate></item><item><title><![CDATA[Why Zod 2 isn't leaving beta]]></title><description><![CDATA[<p>Zod's v2 has been in beta since September 2020 (around 3 months as of this writing). The people are starting to talk.</p>
<!-- Sadly I've decided to _retire_ Zod 2. It will never leave beta. Instead we're jumping to straight to Zod 3, which will have a (hopefully far shorter) beta period before becoming a stable `zod@3` release. Let's talk about why. -->
<h2 id="thebackground">The background</h2>
<p>The reason Zod 2 has been in beta for so long (well, one of the reasons anyway!) is that I’ve been increasingly unsatisfied with Zod’s implementation of transformers. Transformers are a mechanism within Zod to convert data from one type to another. For the sake of rigor, I decided it was paramount for transformers to encapsulate both pre- and post-transform validation; to create one, you gave it an input schema <code>A</code>, and output schema <code>B</code>, and a transformation function <code>(arg: A)=&gt;B</code>.</p>
<p>Multiple issues have cropped up that have led me to re-evaluate the current approach. At best, transformers are a huge footgun and at worst they’re fundamentally flawed.</p>
<!-- Unfortunately my initial design for transformers was...rather poor. I want to reimplement them but there will be breaking changes. Under this proposal, Zod 2 will never come out of beta and we'll jump straight to Zod 3 with a better implementation of transformers. Skip to "New Implementation" for details. -->
<!-- ## TLDR

Transformers are poorly designed. I want to reimplement them but there will be breaking changes. Under this proposal, Zod 2 will never come out of beta and we'll jump straight to Zod 3 with a better implementation of transformers. Skip to "New Implementation" for details.

The reason Zod 2 has been in beta for so long (well, one of the reasons anyway!) is that I’ve been increasingly unsatisfied with Zod’s implementation of transformers. Multiple issues have cropped up that have led me to re-evaluate the current approach. At best, transformers are a huge footgun and at worst they’re fundamentally flawed. -->
<h2 id="context">Context</h2>
<p>In version 1, a Zod schema simply let you define a data type you'd like to validate. Under the hood, it magically inferred the static TypeScript type for your schema. More technically, there is a base class (<code>ZodType</code>) that tracked the inferred type in a generic parameter called <code>Type</code>.</p>
<p><img src="https://user-images.githubusercontent.com/3084745/101593584-03bc6f00-39a5-11eb-8a66-5e7e84103290.png" alt="Screen Shot 2020-12-08 at 5 09 36 PM" /></p>
<p>Transformers break this assumption. The accept an <code>Input</code> of one type and return an <code>Output</code> of a different type. To account for this, every Zod schema now had to track both an input and output type. For non-transformers, <code>Input</code> and <code>Output</code> are the same. For transformers only, these types are different.</p>
<p>Let's look at <code>stringToNumber</code> as an example:</p>
<pre><code class="ts language-ts">const stringToNumber = z.transformer(z.string(), z.number(), (val) =&gt;
  parseFloat(val)
);
</code></pre>
<p>For <code>stringToNumber</code>, Input is <code>string</code> and Output is <code>number</code>. Makes sense.</p>
<p>What happens when you pass a value into <code>stringToNumber.parse</code>?</p>
<ol>
<li>The user passes a value into <code>stringToNumber.parse</code></li>
<li>The transformer passes this value through the <code>parse</code> function of its <em>input schema</em> (z.string()). If there are parsing errors, it throws a ZodError</li>
<li>The transformer takes the output from that and passes it into the transformation function (<code>val =&gt; parseFloat(val)</code>)</li>
<li>The transformer takes the output of the transformation and validates it against the output schema (z.number())</li>
<li>The result of that call is returned</li>
</ol>
<p>Here's the takeaway: for a generic transformer <code>z.transformer(A, B, func)</code>, where A and B are Zod schemas, the argument of <code>func</code> should be the <code>Output</code> type of A and the return type is the <code>Input</code> type of B. This lets you do things like this:</p>
<pre><code class="ts language-ts">const stringToNumber = z.transformer(z.string(), z.number(), (val) =&gt;
  parseFloat(val)
);
const numberToBoolean = z.transformer(
  z.number(),
  z.boolean(),
  (val) =&gt; val &gt; 25
);
const stringToNumberToBoolean = z.transformer(
  stringToNumber,
  numberToBoolean,
  (val) =&gt; 5 * val
);
</code></pre>
<h2 id="theproblemswithtransformers">The problems with transformers</h2>
<p>After implementing transformers, I realized transformers could be used to implement another much-requested feature: default values. Consider this:</p>
<pre><code class="ts language-ts">const stringWithDefault = z.transformer(
  z.string().optional(),
  z.string(),
  (val) =&gt; val || 'trout'
);
stringWithDefault.parse('marlin'); // =&gt; "marlin"
stringWithDefault.parse(undefined); // =&gt; "trout"
</code></pre>
<p>Voila, a schema that accepts <code>string | undefined</code> and returns <code>string</code>, substituting the default "trout" if the input is ever undefined.</p>
<p>So I implemented the <code>.default(val:T)</code> method in the ZodType base class as below (partially simplified)</p>
<pre><code class="ts language-ts">default(def: Input) {
  return ZodTransformer.create(this.optional(), this, (x: any) =&gt; {
    return x === undefined ? def : x;
  });
}
</code></pre>
<p>Do you see the problem with that? I didn’t. Neither did anyone who read through the <a href="https://github.com/colinhacks/zod/issues/100">Transformers RFC</a> which I left open for comment for a couple months before starting on implementation.</p>
<p>This implementation quickly gets wonky when you try to pass <em>an existing transformer</em> in as the input or output. In other words, transformers aren't composable. Let's see what happens if we try to set a default value on <code>stringToNumber</code>.</p>
<pre><code class="ts language-ts">stringToNumber.default('3.14');

// EQUIVALENT TO
const defaultedStringToNumber = z.transformer(
  stringToNumber.optional(),
  stringToNumber,
  (val) =&gt; (val !== undefined ? val : '3.14')
);

defaultedStringToNumber.parse('5');
/* { ZodError: [
  {
    "code": "invalid_type",
    "expected": "string",
    "received": "number",
    "path": [],
    "message": "Expected string, received number"
  }
] */
</code></pre>
<p>Let’s walk through why this fails. The input ("5") is first passed into the transformer input (<code>stringToNumber.optional()</code>). This converts the string <code>"5"</code> to the number <code>5</code>. This is then passed into the transformation function. But wait: <code>val</code> is now <code>number | undefined</code>, but the transformer function needs to return a <code>string</code>. Otherwise, if we pass <code>5</code> into <code>stringToNumber.parse</code> it’ll throw. So we need to convert <code>5</code> back to <code>"5"</code>. That may seem easy in this toy example but it’s not possible in the general case. Zod can’t know how to magically undo the transformation function.</p>
<p>In practice, the current definition of <code>default</code> in ZodType shouldn’t have even been possible. The only reason the type checker didn’t catch this bug is because there are a regrettable number of <code>any</code>s floating around in Zod. It’s not a simple matter to switch them all to <code>unknown</code>s either; I’ve had to use <code>any</code> in several instance to get type inference and certain generic methods to work properly. I’ve tried multiple times to reduce the number of <code>any</code>s but I’ve never managed to crack it.</p>
<p>It’s possible this is a one-off issue. I could find some other way to implement <code>.default()</code> that doesn’t involve transformers. Unfortunately this isn’t even the only problem in Zod’s implementation.</p>
<h2 id="thetransformmethod">The <code>.transform</code> method</h2>
<p>Initially the only way to define transformers was with <code>z.transformer(A, B, func)</code>. Eventually I implemented a convenience method you can use like this:</p>
<pre><code class="ts language-ts">z.string().transform(z.number(), (val) =&gt; parseFloat(val));

// equivalent to
z.transformer(z.string(), z.number(), (val) =&gt; parseFloat(val));
</code></pre>
<p>Some users were executing multiple transforms in sequence without changing the actual data type:</p>
<pre><code class="ts language-ts">z.string()
  .transform(z.string(), (val) =&gt; val.toLowerCase())
  .transform(z.string(), (val) =&gt; val.trim())
  .transform(z.string(), (val) =&gt; val.replace(' ', '_'));
</code></pre>
<p>To reduce the boilerplate here, it was recommended that I overload the method definition to support this syntax:</p>
<pre><code class="ts language-ts">z.string()
  .transform((val) =&gt; val.toLowerCase())
  .transform((val) =&gt; val.trim())
  .transform((val) =&gt; val.replace(' ', '_'));
</code></pre>
<p>If the first argument is a function instead of a Zod schema, Zod should assume that the transformation doesn’t transform the type. In other words, <code>z.string().transform((val) =&gt; val.trim())</code> should be equivalent to <code>z.string().transform(z.string(), (val) =&gt; val.trim())</code>. Makes sense.</p>
<p>Consider using this method on a transformer:</p>
<pre><code class="ts language-ts">stringToNumber.transform(/* transformation_func */);
</code></pre>
<p>What type signature do you expect for <code>transformation_func</code>?</p>
<p>Most would probably expect <code>(arg: number)=&gt;number</code>. Some would expect <code>(arg: string)=&gt;string</code>. Neither of those are right; it’s <code>(arg: number)=&gt;string</code>. Yes really. The transformation function expects an input of <code>number</code> (the Output of <code>stringToNumber</code>) and a return type of <code>number</code> (the Input of <code>stringToNumber</code>). This type signature is a natural consequence of a series of logical design decisions, but the end result is dumb. Well done, self.</p>
<p>Intuitively, you should be able to append <code>.transform(val =&gt; val)</code> to any schema. Surely passing in the identity function to a method called <code>.transform()</code> should be a no-op? Unfortunately due to how transformers are implemented, that's not always possible.</p>
<h3 id="morecomplicatedexamples">More complicated examples</h3>
<p>These strange issues composability issues also make it difficult to write any generic functions on top of Zod (of which <code>.transform</code> and <code>.default</code> are two examples). Others have encountered similar issues, like <a href="https://github.com/colinhacks/zod/issues/199">here</a> and <a href="https://github.com/colinhacks/zod/issues/213">here</a>. For the sake of simplicity, I won't break down the nuances of those cases; the underlying issue is the composability problem I described above.</p>
<h2 id="whenshoulddefaultsevenbeapplied">When should defaults even be applied?</h2>
<p>Strangely, there doesn't appear to be a consensus expectation on whether default values should be "applied" pre- or post-transformations. Consider the simple <code>stringToNumber</code> example; should <code>stringToNumber.default(/* value */)</code> accept a number or a string?</p>
<p>As implemented in Zod 2 it should accept <code>string</code> (the <code>Input</code> of the output schema of <code>stringToNumber</code>). In this case, the schema looks at the value passed into parse, checks if it's <code>undefined</code>, and, if so, hot-swaps in the default value. That value is then passed through all the transformers and refinements downstream (in this case, being converted to a <code>number</code>) by the non-optional <code>stringToNumber</code>. Remember, as implemented, this is how <code>stringToNumber.default()</code> returns:</p>
<pre><code class="ts language-ts">stringToNumber.default(val);

// equivalent to

z.transformer(stringToNumber.optional(), stringToNumber, (val) =&gt;
  val !== undefined ? val : '3.14'
);
</code></pre>
<p>But some users expect <code>stringToNumber.default</code> to accept a number; they assume that a default value "short-circuits" the parsing logic. If you pass <code>undefined</code> into <code>.parse()</code>, the parsing should "return early" and return the value. In this case, taht value should be a <code>number</code> because that's the ultimate expected return type of the transformer returned by <code>stringToNumber.default(val)</code>.</p>
<h2 id="apathforward">A path forward</h2>
<blockquote>
  <p>Note from March 2021: the approach described below also had issues! Ultimately the implementation used in Zod 3 was slightly different.</p>
</blockquote>
<p>I just created <a href="https://github.com/colinhacks/zod/issues/264">an RFC</a> describing a plan to improve this state of affairs. The idea was to have each Zod schema contain a list of post-parse transformation functions. When a value is passed into <code>.parse</code>, Zod will type check the value, then pass it through the transform chain. This is the approach Yup uses.</p>
<pre><code class="ts language-ts">const schema = z
  .string()
  .transform((val) =&gt; val.length)
  .transform((val) =&gt; val &gt; 100);

type In = z.input&lt;typeof schema&gt;; // string
type Out = z.input&lt;typeof schema&gt;; // boolean
</code></pre>
<p>Unlike before, Zod doesn’t validate the data type between each transform. We’re relying on the TypeScript engine to infer the correct type based on the function definitions. In this sense, Zod is behaving just like I intended; it’s acting as a reliable source of type safety that lets you confidently implement the rest of your application logic — including transforms. Re-validating the type between each transform is overkill; TypeScript’s type checker already does that.</p>
<p>Each schema will still have an Input (the inferred type of the schema) and an Output (the output type of the last transform in the chain). But because we’re avoiding the weird hierarchy of ZodTransformers everything behaves in a much more intuitive way.</p>
<p>One interesting ramification is that you could interleave transforms and refinements. Zod could keep track of the order each mod/refinement was added and execute them all in sequence:</p>
<pre><code class="ts language-ts">const schema = z
  .string()
  .transform((val) =&gt; parseFloat(val))
  .refine((val) =&gt; val &gt; 25, { message: 'Too short' })
  .transform((val) =&gt; `${val}`)
  .refine((val) =&gt; /^\d+$/.test(val), { message: 'No floats allowed' });
</code></pre>
<p>We'll have to wait and see how this turns out!</p>
<p><strong>Update from March 2021</strong></p>
<p>This is the general approach being used in the new Zod 3 alpha release. There are some small implementational details —&nbsp;refinements and transforms are now "housed" within the ZodEffects wrapper class instead of being attached directly to the schema.</p>
<p>A note on default values: Zod no longer uses transforms to implement default values. Transforms are only capable of implementing <em>post-parse defaults</em>: parse the input, then swap in the default value if the result is <code>undefined</code>. But I've decided that <em>pre-parse defaults</em> make more sense (and are more expected by users). That is&nbsp;—&nbsp;if the input is <code>undefined</code> swap in the default value, and <em>then</em> finish the parsing process (which includes refining and transforming). As such, the defaulting logic now lives in the ZodOptional class.</p>]]></description><link>https://colinhacks.com/essays/why-zod-2-isnt-leaving-beta</link><guid isPermaLink="false">/essays/why-zod-2-isnt-leaving-beta</guid><category><![CDATA[Zod]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Colin McDonnell]]></dc:creator><pubDate>Tue, 08 Dec 2020 07:00:00 GMT</pubDate></item><item><title><![CDATA[Next.js, nested routes, and the war on SPAs]]></title><description><![CDATA[<blockquote>
  <p><strong>Edit: 2022</strong> The issues described here have been largely solved by <a href="https://nextjs.org/blog/layouts-rfc">layouts</a> in Next.js 13.</p>
</blockquote>
<p>I recently set out to implement a single-page application (SPA) on top of Next.js.</p>
<p>To clarify, I'm using the term "SPA" in a very specific way. I'm referring to the "classical" SPA model: an application that handles <em>all</em> data fetching, rendering, and routing entirely client-side.</p>
<p>Turns out, Next.js does <em>not</em> make this easy.</p>
<blockquote>
  <p>To actually learn <em>how</em> to build a single-page application on top of Next.js, checkout my tutorial! <a href="https://colinhacks.com/essays/building-a-spa-with-nextjs">Building a single-page application with Next.js</a>.</p>
</blockquote>
<h3 id="whynotusenextsrouter">Why not use Next's router</h3>
<p>Next's built-in router isn't as flexible as React Router! React Router lets you nest routers hierarchically in a flexible way. It's easy for any "parent" router to share data and provide an outer layout to its "child" routes. This is true for both top-level routes (e.g. <code>/about</code> and <code>/team</code>) and nested routes (e.g. <code>/settings/team</code> and <code>/settings/user</code>).</p>
<p>This isn't possible with Next's built-in router! Instead, <em>all</em> shared state and layout must be initialized in your custom <code>_app.tsx</code> component. It's equivalent to building an app with a single, top-level React Router. Nominally, Next.js lets you define "nested routes" (e.g. <code>pages/settings/team.tsx</code>), but in practice it "flattens" all those routes. The only code that's shared is the stuff you can fit into <code>_app.tsx</code>.</p>
<p>There are some established patterns for circumventing this limitation, notably those described in <a href="https://adamwathan.me/2019/10/17/persistent-layout-patterns-in-nextjs/">this post</a> by Adam Wathan. If you are dead-set on using Next's routing system, you should look at those approaches. But in my opinion they're agregiously hacky.</p>
<h3 id="whyisthissohard">Why is this so hard?</h3>
<p>I finally got React Router to work inside Next.js — check out <a href="https://colinhacks.com/essays/building-a-spa-with-nextjs">my tutorial</a> for details — but there were some thorny issues along the way. The guidance in the documentation is…scant, to put it mildly. It boils down to "use a custom App".</p>
<p><img src="/nextjs_custom_app.png" alt="Next.js Use A Custom App" /></p>
<p>That's it. Not particularly enlightening. Or detailed.</p>
<p>I found this to be pretty strange. Aren't SPAs still the dominant model for building React-based applications? Didn't Next.js just run a wildly successful conference with 60,000 virtual attendees? Didn't they just raise a \$21M Series A? Am I the only person struggling with this? Why is this so hard?</p>
<p>My answer: Vercel doesn't really want you to build SPAs, at least not with client-side routing. Theyr're a server hosting company, so they want you to use servers. To that end, I believe they consciously emphasize the server-side rendering (SSR) approach and de-emphasize the SPA approach.</p>
<h3 id="whataboutssg">What about SSG?</h3>
<p>An astute reader may ask "Why would Next.js try to de-popularize SPAs but encourage static site generation (SSG)?" Like SPAs, statically generated sites don't involve servers and thus doesn't make Vercel much money. Good point. This is where my theory veers into conspiracy and madness.</p>
<p>I suspect SSG is a strategic marketing play. A huge fraction of developers will build a static website at some point in their career. Having built their personal website with Next.js, they're more likely to choose Next.js down the road when designing their shiny new SaaS app. And since it's against Vercel's fair use policy to run a commercial enterprise on the free tier, all these new companies will eventually convert to a paid plan. They're playing the long game.</p>
<p>I don't think the SSG functionality in Next.js was created solely for marketing reasons; that's ludicrous. I think Next.js is the best way to build static site; I wrote a <a href="https://colinhacks.com/essays/devii">1500-word article</a> to that effect just 5 months ago. In fact, I believe Vercel is laser-focused on improving the developer experience, perhaps more than any other company. That's exactly why I don't believe the lack of documentation on client-side routing is merely an oversight. The strength of the rest of their documentation makes this hole even more glaring.</p>
<p>None of this is <em>bad</em>, per se. Vercel and Next.js originated, in part, as a reaction to the SPA-centricity of React development pre-2016. They're under no obligation to document, advertise, or encourage the usage of SPA paradigm on their platform, especially if it works against their own interests. And all told, their free tier is extremely generous.</p>
<p>But remember that, at the end of the day, Vercel is a hosting company with a vested financial interest in pushing server-side rendering and de-popularizing SPAs. If someone tried to learn full-stack web development by reading the Next.js documentation, they wouldn't even know that SPAs are an option. So keep that in mind as you design your next project!</p>
<hr />
<blockquote>
  <p>Addendum: The section below was added shortly after initial publication.</p>
</blockquote>
<p>In response to this article the lead maintainer of Next.js, Tim Neutkens, provided this counterpoint on Twitter:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">All our marketing and communication around this have been around making sure you don’t use SSR if you don’t need it, e.g. by adding revalidate (incremental generation) and fallback (on-demand generation)</p>&mdash; Tim (@timneutkens) <a href="https://twitter.com/timneutkens/status/1321906247722033157?ref_src=twsrc%5Etfw">October 29, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>This actually sheds light on another interesting aspect of Vercel's SSG push.</p>
<p>Recently Next.js has introduced support for "dynamic static generation" with the <code>fallback</code> flag and incremental static regeneration with the <code>revalidate</code> flag. Tim presented these features as evidence that Next.js is helping developers avoid server-side rendering if it isn't absolutely necessary. But using either of these features requires Vercel to spin up a serverless function for your app. In other words, Next is blurring the line between SSG and SSR.</p>
<p>If you rely on these features, you've left the "pure" SSG paradigm. Your app now requires a server to work as expected — which means Vercel stands to make money! You can no longer simply <code>next build &amp;&amp; next export</code> your app and deploy to your static hosting service of choice. Moreover, if your project is commercial in nature, you now must upgrade to a Pro plan (if hosting on Vercel).</p>
<hr />
<h2 id="wrapup">Wrap up</h2>
<p>If you want to actually learn <em>how</em> to build a single-page application on top of Next.js, checkout my tutorial <a href="https://colinhacks.com/essays/building-a-spa-with-nextjs">Building a single-page application with Next.js
</a>.</p>
<p>Hopefully I didn't come off as too critical of Vercel. Next.js is phenomenal and I love it. If I didn't I would never have gone through all this trouble!</p>
<p>The "comments section" for this post is this Twitter thread. Jump in and yell at me for all the bad takes in this post!</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">✨ NEW BLOG POST ✨<br><br>It started as a simple &quot;How to build a SPA with Next.js&quot; tutorial.<br><br>It turned into a fevered rant about Vercel, Next.js, perverse incentives, and the future of the SPA paradigm. Oops.<a href="https://t.co/hrRtKojezH">https://t.co/hrRtKojezH</a></p>&mdash; Colin McDonnell (@colinhacks) <a href="https://twitter.com/colinhacks/status/1321871817095684097?ref_src=twsrc%5Etfw">October 29, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>If you're into Next.js or TypeScript, follow me <a href="https://twitter.com/colinhacks">on Twitter @colinhacks</a>! I build and maintain open-source tools like <a href="https://github.com/colinhacks/zod">Zod</a> (a TypeScript validation library) and <a href="https://github.com/colinhacks/trpc">tRPC</a> (a tool for building end-to-end typesafe APIs with TypeScript). Or subscribe to the newsletter to get notified when I publish new posts!</p>
<p><br/></p>
<!-- author -->
<p><br/></p>
<!-- signupfield -->
<p><br/></p>]]></description><link>https://colinhacks.com/essays/vercel-nextjs-spa</link><guid isPermaLink="false">/essays/vercel-nextjs-spa</guid><category><![CDATA[Next.js]]></category><category><![CDATA[React]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Colin McDonnell]]></dc:creator><pubDate>Thu, 29 Oct 2020 21:04:40 GMT</pubDate></item><item><title><![CDATA[Building a single-page application with Next.js and React Router]]></title><description><![CDATA[<blockquote>
  <p><strong>Update 2023</strong> — Updated for Next.js 13, React Router 6, and React 18.</p>
  <p><strong>Update 2022</strong> — Next.js 13 now supports nested routes via the <code>/app</code> directory. However, it requires a server to render React Server Components, and thus doesn't qualify as a "single-page application" as defined in this post.</p>
</blockquote>
<p>I recently set out to implement a single-page application (SPA) on top of Next.js.</p>
<p>To clarify, I'm using the term "SPA" in a very specific way. I'm referring to the "old school" SPA model: an application that handles <em>all</em> data fetching, rendering, and routing entirely client-side.</p>
<p>Turns out, Next.js does <em>not</em> make this easy.</p>
<h2 id="whynotusenextjsrouting">Why not use Next.js routing?</h2>
<p>Next.js is not as flexible as React Router! React Router lets you nest routers hierarchically in a flexible way. It's easy for any "parent" router to share data with all of its "child" routes. This is true for both top-level routes (e.g. <code>/about</code> and <code>/team</code>) and nested routes (e.g. <code>/settings/team</code> and <code>/settings/user</code>).</p>
<p>This isn't possible with Next's built-in router! Instead, <em>all</em> shared state and layout must be initialized in your custom <code>_app.tsx</code> component. It's equivalent to building an app with a single, top-level React Router. This is true despite the fact that Next.js nominally lets you define "nested routes" (e.g. <code>pages/settings/team.tsx</code>). In practice it "flattens" all those routes.</p>
<p>There are complex, hacky ways of achieving this in Next, including several described <a href="https://adamwathan.me/2019/10/17/persistent-layout-patterns-in-nextjs/">here</a> by Adam Wathan. If you are dead-set on using Next's routing system, you should look at those approaches.</p>
<!-- More succinctly, Next.js makes it easy to share data/layouts between your top-level routes ()
SPAs are still the best choice for building interactive web software without strict SEO requirements. Typically SaaS companies use separate domains for their marketing website (thingy.io) and their actual product (app.thingy.io). This frees them up to build their product with no regard for SEO.  -->
<h2 id="whyusenextjsatall">Why use Next.js at all?</h2>
<p>Because it's awesome! You can split your bundle with dynamic imports, which solves one of the biggest performance issues associated with large SPAs. You can easily deploy to Vercel. You can build your API using API Routes, instead of maintaining separate client and server codebases. Plus there's that snazzy new <code>&lt;Image&gt;</code> component!</p>
<p>But the killer feature of Next.js for me is the zero-config client-server codesharing.</p>
<p>The ability to share code and typings between my client and server is a <em>massive win</em>. The developer experience is 10x better than Lerna and 100x better than Yarn Workspaces (trust me). It's the enabling feature behind my new library <a href="https://github.com/colinhacks/trpc">github.com/colinhacks/trpc</a>, which lets you build an end-to-end typesafe data layer. Better yet, it abstracts away the API entirely and lets you call your server-side functions directly from your client. I'm biased, but it's extremely cool. Check it out at <a href="https://github.com/colinhacks/trpc">github.com/colinhacks/trpc</a>! ✨</p>
<!--
### Why this is so hard

I finally got it working —&nbsp;skip to the Implementation section for the details&nbsp;— but there were some thorny issues along the way. How do we implement a persistent state management store? How do we share an outer layout among multiple pages? And for the love of god, how do we get react-router to play nice with Next.js routing?

The answer provided by the documentation is a bit brusqe: "use a custom App".

![Next.js Use A Custom App](/nextjs_custom_app.png)

That's it. Not particularly enlightening, or detailed. Let's rectify that! -->
<h2 id="implementation">Implementation</h2>
<p>For highly interactive "dashboard-style" SaaS development, the dominant paradigm is still the SPA. Unfortunately Next.js is (nearly) incompatible with the SPA paradigm for one fundamental reason: routing. Next.js ships with its own built-in page-based routing system, whereas the typical SPA relies on client-side routing, typically using a library like <a href="https://reactrouter.com/">react-router</a>.</p>
<p>Below I will walk through all the errors I encountered while trying to use React Router inside a Next app and how I fixed them. For demonstration purposes, I'll be using the simple router below, which is a simplified version of the "Basic Routing" example from the <a href="https://reactrouter.com/web/guides/quick-start#quick-start-1st-example-basic-routing">react-router docs</a>.</p>
<pre><code class="tsx language-tsx">import React from 'react';
import {BrowserRouter as Router, Routes, Route, Link} from 'react-router-dom';

export default function App() {
  return (
    &lt;Router&gt;
      &lt;div&gt;
        &lt;ul&gt;
          &lt;li&gt;
            &lt;Link to="/"&gt;Home&lt;/Link&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;Link to="/about"&gt;About&lt;/Link&gt;
          &lt;/li&gt;
        &lt;/ul&gt;

        &lt;Routes&gt;
          &lt;Route path="/about" element={&lt;h1&gt;About&lt;/h1&gt;} /&gt;
          &lt;Route path="/" element={&lt;h1&gt;Home&lt;/h1&gt;} /&gt;
        &lt;/Routes&gt;
      &lt;/div&gt;
    &lt;/Router&gt;
  );
}
</code></pre>
<h2 id="browserhistoryneedsadom"><code>Browser history needs a DOM</code></h2>
<p>Let's start by creating a new Next.js app, installing React Router (<code>npm install react-router-dom</code>), and pasting the router code above into <code>pages/index.tsx</code>. When you run <code>next dev</code> you'll immediately get a not-so-subtle <code>Invariant failed: Browser history needs a DOM</code> error.</p>
<p><img src="/nextjs_no_dom.png" alt="Browser history needs a DOM" /></p>
<p>This happens because Next.js tries to pre-render your pages on the development server in development mode. React Router, on the other hand, requires access to the global <code>window</code> object provided by the browser. Because <code>window</code> isn't available in the server environment, React Router craps out.</p>
<p>To fix this, we need to (<em>drumroll please</em>) use a <a href="https://nextjs.org/docs/advanced-features/custom-app">custom App</a>! Add a file called <code>pages/_app.tsx</code> (or <code>pages/_app.js</code> if you're a monster). Let's try a naive approach to fixing this problem. If <code>window</code> isn't defined, return <code>null</code>; otherwise, render the page.</p>
<pre><code class="tsx language-tsx">import {AppProps} from 'next/app';
import {useEffect, useState} from 'react';

function App({Component, pageProps}: AppProps) {
  if (typeof window === 'undefined') return null;
  return &lt;Component {...pageProps} /&gt;;
}
export default App;
</code></pre>
<h2 id="hydrationfailed"><code>Hydration failed</code></h2>
<p>Reload the page and you'll see a new error.</p>
<p><img src="/hydration_failed.png" alt="Hydration failed" /></p>
<p>When you load a page of your Next.js app, Next.js 1) tries to pre-render it on the server, 2) sends the result to the browser, and 3) "re-hydrates" the page in the browser. Re-hydration means the page is re-rendered again on the browser and compared against the version that was rendered on the server. If they disagree, React throws an error.</p>
<p>Let's get a little more clever.</p>
<pre><code class="ts language-ts">import {AppProps} from 'next/app';
import {useEffect, useState} from 'react';

function App({Component, pageProps}: AppProps) {
  const [render, setRender] = useState(false);
  useEffect(() =&gt; setRender(true), []);
  return render ? &lt;Component {...pageProps} /&gt; : null;
}
export default App;
</code></pre>
<p>This approach <code>useState</code> and <code>useEffect</code>. When Next.js renders a page on the server, it doesn't execute any <code>useEffect</code> calls; it just renders the page using default values and returns the result.</p>
<p>When this page is loaded by the browser will do the same thing: render the page using default values. Because the default value of <code>render</code> is <code>false</code>, the browser will initially render <code>null</code>. This is great, because it agrees with the server-rendered version of the page.</p>
<p>Immediately after, the browser will execute the <code>useEffect</code> call, which will set <code>render</code> to <code>true</code>. The page will now re-render in its full glory. We do waste a little time rendering <code>null</code> initially, but it's a microscopic amount of time: under a millisecond.</p>
<h2 id="404pagenotfound"><code>404 Page Not Found</code></h2>
<p>React Router and the Next.js router can interact in strange and unexpected ways. To understand the problem, you need to understand the different between client-side routing and "server-side routing".</p>
<p>Currently <code>pages/index.tsx</code> implements a router with 2 "pages": the home page (<code>/</code>) and an about page (<code>/about</code>).</p>
<p>Try this:</p>
<ol>
<li>Go to <code>localhost:3000</code></li>
<li>Click the "About" link</li>
</ol>
<p>You should see the URL changes to <code>localhost:3000/about</code> and we see the About "page". Great! But:</p>
<ol start="3">
<li>Refresh the page</li>
</ol>
<p>Now we get a 404. What gives? <code>localhost:3000/about</code> worked just fine before the reload.</p>
<p>As far as Next.js is concerned, the homepage (<code>localhost:3000</code>) is the <em>only</em> page that exists. Once that page loads, react-router siezes control of the URL bar. When you click a react-router <code>&lt;Link&gt;</code>, it changes the URL programmatically, but the page is never fully reloaded.</p>
<p>But when you refresh the page, the browser asks Next.js to provide the content for <code>localhost:3000/about</code>. Since we haven't implemented <code>pages/about.tsx</code>, Next.js gives up and throws a 404.</p>
<p>The solution: <a href="https://nextjs.org/docs/api-reference/next.config.js/rewrites">Rewrites</a>!</p>
<p>This feature was made available in Next.js 9.5. Rewrites are like "URL aliases"; you can tell Next.js to remap some "source" URL to a different "destination" URL. You can configure them in <code>next.config.js</code> file (if that file doesn't exist you should create it in the root of your project). Add the following contents:</p>
<pre><code class="ts language-ts">// next.config.js
module.exports = {
  async rewrites() {
    return [
      {
        source: '/:any*',
        destination: '/',
      },
    ];
  },
};
</code></pre>
<p>This rewrite rule maps <em>all incoming requests</em> (<code>/:any*</code>) to the homepage (<code>/</code>).</p>
<p>Now, if you restart the server and navigate to <code>localhost:3000/about</code> your <code>index.tsx</code> homepage should render. Yay!</p>
<h2 id="mixingclientandserversiderendering">Mixing client- and server-side rendering</h2>
<p>Important note: Next.js <em>ignores</em> this rewrite rule if the incoming request corresponds to an existing page in the <code>pages</code> directory. For instance, if you add <code>pages/about.tsx</code> later, Next.js will stop rewriting <code>/about</code> to the homepage.</p>
<p><img src="/nextjs_pages.png" alt="Nextjs rewrite documentation" /></p>
<p>This is actually really cool! For starters, it lets you use API Routes without any additional configuration. But more importantly, it lets you mix-and-match the SPA, SSR, and SSG paradigms at will! For instance, to add a <code>/settings</code> page that is server-side rendered:</p>
<ol>
<li>Create and implement <code>pages/settings.tsx</code></li>
<li>Add a <code>getServerSideProps</code> fetcher</li>
<li>Add a link to <code>/settings</code> using the <code>&lt;Link&gt;</code> component from <code>next/link</code> (<em>not</em> the one from <code>react-router-dom</code>!)</li>
</ol>
<p>Now you have a single server-side rendered page inside your SPA! Can't do that with create-react-app 🙃</p>
<h2 id="wrapup">Wrap up</h2>
<p>Big shout out to Tanner Linsley's <a href="https://gist.github.com/tannerlinsley/65ac1f0175d79d19762cf06650707830">GitHub Gist</a> on converting from CRA to Next.js, which inspired and informed this post.</p>
<p>You can see ALL the code I used here in the demo repo at <a href="https://github.com/colinhacks/nextjs-react-router">github.com/colinhacks/nextjs-spa</a>.</p>
<p>The "comments section" for this post is this Twitter thread. Chime in! I love discussing Next.js, TypeScript, open-source software, and just about everything else!</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">✨ NEW BLOG POST ✨<br><br>It started as a simple &quot;How to build a SPA with Next.js&quot; tutorial.<br><br>It turned into a fevered rant about Vercel, Next.js, perverse incentives, and the future of the SPA paradigm. Oops.<a href="https://t.co/hrRtKojezH">https://t.co/hrRtKojezH</a></p>&mdash; Colin McDonnell (@colinhacks) <a href="https://twitter.com/colinhacks/status/1321871817095684097?ref_src=twsrc%5Etfw">October 29, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>If you're into Next.js or TypeScript, follow me <a href="https://twitter.com/colinhacks">on Twitter @colinhacks</a>! I build and maintain open-source tools like <a href="https://github.com/colinhacks/zod">Zod</a> (a TypeScript validation library) and <a href="https://github.com/colinhacks/trpc">tRPC</a> (a tool for building end-to-end typesafe APIs with TypeScript). Or subscribe to the newsletter to get notified when I publish new posts!</p>
<p><br/></p>
<!-- author -->
<p><br/></p>
<!-- signupfield -->
<p><br/></p>]]></description><link>https://colinhacks.com/essays/building-a-spa-with-nextjs</link><guid isPermaLink="false">/essays/building-a-spa-with-nextjs</guid><category><![CDATA[Next.js]]></category><category><![CDATA[React]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Colin McDonnell]]></dc:creator><pubDate>Thu, 29 Oct 2020 05:51:10 GMT</pubDate></item><item><title><![CDATA[Authenticated server-side rendering with Next.js and Firebase]]></title><description><![CDATA[<!-- > **Update: November 29, 2020**
>
> I have an idea for a SaaS product related to Next.js and authentication, and I'm looking for a co-founder! Ideally you'd be intimately familiar with Next.js, TypeScript, and SQL, and perhaps have some previous entrepreneurial experience. I'm an open-source maintainer and a previous YC founder, and I feel strongly about finding the right partner before starting a new endeavor. If you're interested shoot me an email at colin@colinhacks.com with a bit about yourself! -->
<blockquote>
  <p><strong>Feb 24, 2021</strong>: a previous version of this post contained a bug where a new cookie would be created each time you signed out and back in again. These (identical) cookies would stack up cause problems. If you're having issues, pass the <code>{ path: '/' }</code> option to <code>nookies.set</code> call&nbsp;— details below.</p>
</blockquote>
<p>I had a hell of a time figuring out how to get Firebase Auth, Next.js, tokens, cookies, and <code>getServerSideProps</code> to play nice together. I'm hoping this breakdown will spare you the same suffering!</p>
<blockquote>
  <p>To jump straight to the sample repo, head to https://github.com/colinhacks/next-firebase-ssr 🤙</p>
</blockquote>
<!-- Next.js puts so the single-page application model is all I've ever known. I've never had to deal with cookies in my life. I've never even implemented my own authorization system (password hashing, token generation, yadda yadda) myself; I've always relied on an authorization-as-a-service provider like Auth0 or Firebase. -->
<p>I'm currently building a new app. My goal is to do <em>all</em> data fetching inside <code>getServerSideProps</code> , just like the bad ole days of HTML templating. There are a lot of reasons for this.</p>
<ul>
<li>It reduces the cognitive overhead compared to the classic JAMstack approach. The client-server dichotomy is gone — it's just a server! So I can deploy my entire application with a single command.</li>
<li>It solves the stale bundle problem. With a fully client-side rendered app, an user may use your application for days or weeks without doing a full page refresh. If the API changes underneath them, they may send API payloads that are no longer supported.</li>
<li>It strikes a great balance between dynamism and search engine optimization.</li>
<li>The user experience is unbeatable. With good caching and performant fetching, you can swap out the ubiquitous SPA spinner for snappy page loads.</li>
</ul>
<p>As usual, Dan Abramov says it best:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">We’ve reached peak complexity with SPA. The pendulum will swing a bit back to things on the server. But it will be a new take — a hybrid approach with different tradeoffs than before. Obviously I’m thinking React will be a part of that wave.</p>&mdash; Dan Abramov (@dan_abramov) <a href="https://twitter.com/dan_abramov/status/1290289129255624706?ref_src=twsrc%5Etfw">August 3, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>It seems like the SSR-centric approach to building apps with Next.js is gaining steam fast. So I'm extremely confused why the official Next.js <a href="https://github.com/vercel/next.js/tree/canary/examples/with-firebase-authentication">with-firebase-authentication example project</a> doesn't demonstrate how to do it! It only demonstrates how to make authenticated POST requests to an API, not authenticate users inside <code>getServerSideProps</code> .</p>
<p>So below I explain how to use Next.js and Firebase Auth to:</p>
<ul>
<li>sign in users (duh)</li>
<li>generate ID tokens</li>
<li>store those ID tokens as a cookie</li>
<li>auto-refresh the cookie whenever Firebase refreshes the ID token (every hour by default)</li>
<li>implement authenticated routes</li>
<li>authorize the user in <code>getServerSideProps</code></li>
<li>redirect unauthenticated users to a login page from within <code>getServerSideProps</code></li>
</ul>
<p>Let's dive in.</p>
<h2 id="1configureyourfirebasejssdk">#1 Configure your Firebase JS SDK</h2>
<p>You've probably done this already. Should look something like this:</p>
<pre><code class="ts language-ts">// firebaseClient.ts

import * as firebase from 'firebase/app';
import 'firebase/auth';

if (typeof window !== 'undefined' &amp;&amp; !firebase.apps.length) {
  firebase.initializeApp({
    apiKey: 'APIKEY',
    authDomain: 'myproject-123.firebaseapp.com',
    databaseURL: 'https://myproject-123.firebaseio.com',
    projectId: 'myproject-123',
    storageBucket: 'myproject-123.appspot.com',
    messagingSenderId: '123412341234',
    appId: '1:1234123412341234:web:1234123421342134d',
  });
  firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION);
}

export { firebase };
</code></pre>
<blockquote>
  <p>The <code>firebase.apps.length</code> check is a clever way of preventing Next.js from accidentally re-initalizing your SDK when Next.js hot reloads your application!</p>
</blockquote>
<h2 id="2configureyourfirebaseadminsdk">#2 Configure your Firebase Admin SDK</h2>
<p>You've probably done this already too. Check out the Firebase docs for guidance. It'll look something like this:</p>
<pre><code class="ts language-ts">// firebaseAdmin.ts

import * as firebaseAdmin from 'firebase-admin';

// get this JSON from the Firebase board
// you can also store the values in environment variables
import serviceAccount from './secret.json';

if (!firebaseAdmin.apps.length) {
  firebaseAdmin.initializeApp({
    credential: firebaseAdmin.credential.cert({
      privateKey: serviceAccount.private_key,
      clientEmail: serviceAccount.client_email,
      projectId: serviceAccount.project_id,
    }),
    databaseURL: 'https://YOUR_PROJECT_ID.firebaseio.com',
  });
}

export { firebaseAdmin };
</code></pre>
<p>Alternatively you can store the <code>secret.json</code> file elsewhere on your file system and tell Firebase where you find it using the <code>GOOGLE_APPLICATION_CREDENTIALS</code> environment variable. For details check out the Firebase Admin setup documentation <a href="https://firebase.google.com/docs/admin/setup#initialize-sdk">here</a>.</p>
<h2 id="3createacontextandprovider">#3 Create a Context and provider</h2>
<p>Now we're getting to the meat and potatoes. We're going to use React Context to implement the authentication logic. Let's start by writing a simple provider:</p>
<pre><code class="ts language-ts">import { createContext } from 'react';

const AuthContext = createContext&lt;{ user: firebase.User | null }&gt;({
  user: null,
});

export function AuthProvider({ children }: any) {
  const [user, setUser] = useState&lt;firebase.User | null&gt;(null);

  // handle auth logic here...

  return (
    &lt;AuthContext.Provider value={{ user }}&gt;{children}&lt;/AuthContext.Provider&gt;
  );
}
</code></pre>
<p>In the <code>AuthProvider</code> component we initialize a <code>user</code> state and pass it into our React component hierarchy using React Context. If you haven't seen this pattern before, I recommend reading the <a href="https://reactjs.org/docs/context.html">official docs</a> on Context before continuing.</p>
<h3 id="listenfortokenchanges">Listen for token changes</h3>
<p>Now let's add a <code>useEffect</code> hook that initializes our Firebase authentication listener:</p>
<pre><code class="ts language-ts">import nookies from 'nookies';

const AuthContext = createContext&lt;{ user: firebase.User | null }&gt;({
  user: null,
});

export function AuthProvider({ children }: any) {
  const [user, setUser] = useState&lt;firebase.User | null&gt;(null);

  useEffect(() =&gt; {
    return firebase.auth().onIdTokenChanged(async (user) =&gt; {
      if (!user) {
        setUser(null);
        nookies.set(undefined, 'token', '', { path: '/' });
      } else {
        const token = await user.getIdToken();
        setUser(user);
        nookies.set(undefined, 'token', token, { path: '/' });
      }
    });
  }, []);

  return (
    &lt;AuthContext.Provider value={{ user }}&gt;{children}&lt;/AuthContext.Provider&gt;
  );
}
</code></pre>
<p>This is where the magic happens. We're hooking into the <code>firebase.auth().onIdTokenChanged</code> event listener; if you've never used it, it's identical to <code>onAuthStateChanged</code> but it <em>also</em> fires when the user's ID token is refreshed.</p>
<p>In the <code>onIdTokenChanged</code> callback, we check if the user is still signed in. If they are, we set the user with <code>setUser</code> .</p>
<p>Here's the most important part: we also set a <code>token</code> cookie that contains the user's ID token. (To accomplish this, I'm using the excellent <a href="https://github.com/maticzav/nookies"> <code>nookies</code> </a> package by the great and powerful <a href="https://github.com/maticzav">@maticzav</a>.)</p>
<blockquote>
  <p>Feb 24, 2021: passing the <code>{ path: '/' }</code> option to <code>nookies.set</code> prevents your app from creating a new token each time this code runs!</p>
</blockquote>
<p>Now all outgoing requests — both API requests and page navigations! — will contain the user's ID token as a cookie! There's an example of this further down the page.</p>
<h3 id="tokenrefresh">Token refresh</h3>
<p>A previous version of this post incorrectly assumed that Firebase automatically refreshes the ID token on an hourly basis. As it turns out, that's not true. Firebase only does so if it is maintaining an active connect to Firestore or Realtime Database. If you aren't using one of these, services we need to refresh the tokens ourselves.</p>
<p>Below is the final version of our AuthProvider. Note the new <code>useEffect</code> hook that automatically force-refreshes the Firebase token every 10 minutes.</p>
<pre><code class="ts language-ts">import nookies from 'nookies';

const AuthContext = createContext&lt;{ user: firebase.User | null }&gt;({
  user: null,
});

export function AuthProvider({ children }: any) {
  const [user, setUser] = useState&lt;firebase.User | null&gt;(null);

  // listen for token changes
  // call setUser and write new token as a cookie
  useEffect(() =&gt; {
    return firebase.auth().onIdTokenChanged(async (user) =&gt; {
      if (!user) {
        setUser(null);
        nookies.set(undefined, 'token', '', { path: '/' });
      } else {
        const token = await user.getIdToken();
        setUser(user);
        nookies.set(undefined, 'token', token, { path: '/' });
      }
    });
  }, []);

  // force refresh the token every 10 minutes
  useEffect(() =&gt; {
    const handle = setInterval(async () =&gt; {
      const user = firebaseClient.auth().currentUser;
      if (user) await user.getIdToken(true);
    }, 10 * 60 * 1000);

    // clean up setInterval
    return () =&gt; clearInterval(handle);
  }, []);

  return (
    &lt;AuthContext.Provider value={{ user }}&gt;{children}&lt;/AuthContext.Provider&gt;
  );
}
</code></pre>
<h4 id="whyusereactcontext">Why use React Context?</h4>
<p>You may be wondering why you need to use React Context at all. Let's consider a more naive approach to writing this hook.</p>
<pre><code class="ts language-ts">// DONT DO THIS

export const useAuth = () =&gt; {
  const [user, setUser] = useState&lt;firebase.User | null&gt;(null);

  useEffect(() =&gt; {
    return firebase.auth().onIdTokenChanged(async (user) =&gt; {
      if (user) {
        setUser(user);
      } else {
        setUser(null);
      }
    });
  }, []);

  return { user };
};
</code></pre>
<p>This actually works fine. However it would create a new <code>user</code> state variable and a new listener <em>every time you use the hook</em>. We want to make sure the <code>user</code> variable is the same everywhere throughout our application to avoid difficult-to-debug synchronization issues. We only want a <em>single reference</em> to the currently signed in user that is shared throughout our app.</p>
<h2 id="4addyourprovidertoacustomapp">#4 Add your provider to a custom App</h2>
<p>The documentation for setting up a Custom App page with Next.js are at https://nextjs.org/docs/advanced-features/custom-app. It's the ideal place to add "wrapper" code that you want to exist on all other pages — like Context providers.</p>
<pre><code class="tsx language-tsx">// pages/_app.tsx

import type { AppProps } from 'next/app';
import { AuthProvider } from '../auth';

function App({ Component, pageProps }: AppProps) {
  return (
    &lt;AuthProvider&gt;
      &lt;Component {...pageProps} /&gt;
    &lt;/AuthProvider&gt;
  );
}
export default App;
</code></pre>
<h2 id="5createtheuseauthhook">#5 Create the useAuth hook</h2>
<p>Now that the context and provider is set up, our actual <code>useAuth</code> hook is dead simple:</p>
<pre><code class="ts language-ts">export const useAuth = () =&gt; {
  return useContext(AuthContext);
};
</code></pre>
<h2 id="6checkthetokeningetserversideprops">#6 Check the token in getServerSideProps</h2>
<p>Below is a complete working example of how to implement an authenticated route. If the user isn't properly signed in (e.g. if the <code>token</code> cookies doesn't exist or if the token verification fails) the user is redirected to the <code>/login</code> page.</p>
<p>Otherwise, you can confidently fetch and return data belonging to the user!</p>
<pre><code class="tsx language-tsx">// /pages/authenticated.tsx
import React from 'react';
import nookies from 'nookies';
import { InferGetServerSidePropsType, GetServerSidePropsContext } from 'next';

import { firebaseAdmin } from '../firebaseAdmin';

export const getServerSideProps = async (ctx: GetServerSidePropsContext) =&gt; {
  try {
    const cookies = nookies.get(ctx);
    const token = await firebaseAdmin.auth().verifyIdToken(cookies.token);

    // the user is authenticated!
    const { uid, email } = token;

    // FETCH STUFF HERE!! 🚀

    return {
      props: { message: `Your email is ${email} and your UID is ${uid}.` },
    };
  } catch (err) {
    // either the `token` cookie didn't exist
    // or token verification failed
    // either way: redirect to the login page
    ctx.res.writeHead(302, { Location: '/login' });
    ctx.res.end();

    // `as never` prevents inference issues
    // with InferGetServerSidePropsType.
    // The props returned here don't matter because we've
    // already redirected the user.
    return { props: {} as never };
  }
};

export default (
  props: InferGetServerSidePropsType&lt;typeof getServerSideProps&gt;
) =&gt; (
  &lt;div&gt;
    &lt;p&gt;{props.message}&lt;/p&gt;
  &lt;/div&gt;
);
</code></pre>
<h2 id="thefullsolution">The full solution</h2>
<p>If you've made it this far you must think the ideas in this post are pretty useful! Consider retweeting this to get the word out: 🙌</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Wrote up a guide to authenticated server-side rendering with Nextjs and Firebase Auth. <br><br>There&#39; s a surprising lack of documentation or sample code for this use case, given that it may well become a best practice over the next couple years. 1/n<a href="https://t.co/22hVqp8Fy5">https://t.co/22hVqp8Fy5</a></p>&mdash; Colin McDonnell (@colinhacks) <a href="https://twitter.com/colinhacks/status/1295486862899859456?ref_src=twsrc%5Etfw">August 17, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>The full code of this project — plus some other goodies! a sign up form! a logout button! — is available at https://github.com/colinhacks/next-firebase-ssr. Clone that repo to hit the ground running. 🏃🏽‍♂️💨</p>
<p>By the way, I'm currently working on an extremely rad library that lets you define your API with TypeScript and auto-generate a strongly typed SDK that you can safely use on the client. I think it could be a gamechanger for full-stack TypeScript/Next.js development. Join the mailing list below to stay updated.</p>
<!-- signupfield -->]]></description><link>https://colinhacks.com/essays/nextjs-firebase-authentication</link><guid isPermaLink="false">/essays/nextjs-firebase-authentication</guid><category><![CDATA[Next.js]]></category><category><![CDATA[Firebase]]></category><dc:creator><![CDATA[Colin McDonnell]]></dc:creator><pubDate>Sun, 09 Aug 2020 20:52:24 GMT</pubDate></item><item><title><![CDATA[Sponsor pools: a new funding model for open source software]]></title><description><![CDATA[<blockquote>
  <p>Check out the HN discussion <a target="_blank" rel="noopener noreferrer" href="https://news.ycombinator.com/item?id=23981563">here</a>.</p>
  <p>This article has been translated into <a href="https://itnews.org/news_contents/a-new-funding-model-for-open-source-software">Japanese</a> and <a href="https://pngset.com/ru-open-source-software">Russian</a>.</p>
</blockquote>
<p>The existing open-source funding models don't work well for small projects.</p>
<p>Big projects —&nbsp;operating systems, frameworks, CMSs, or fully self-hostable applications — are in a privileged position to extract more value from their users, especially corporate ones. Since entire APIs and products are built on top of them, they inspire enough appreciation (or, more likely, fear of obsolescence!) to net a sustainable income from one-off or monthly donations.¹</p>
<p>But most OSS projects aren't big. The typical project on GitHub is better described as a <strong>utility</strong> — an apt term, given the <a href="https://www.fordfoundation.org/work/learning/research-reports/roads-and-bridges-the-unseen-labor-behind-our-digital-infrastructure/">infrastructural role</a> these projects play in the world. It is a small tool that does one thing really well. Over the course of building a complex application, you may end up using dozens of these utilities — and they'll save you hundreds of hours of development time.</p>
<p>Unfortunately, utilities like these rarely bring in any meaningful amount of money from donations, no matter how widely used or beloved they are. Consider <a href="https://github.com/ReactTraining/react-router">react-router</a>. Even with 41.3k stars on GitHub, 3M weekly downloads from NPM, and nearly universal adoption in React-based single-page applications, it only brings in <a href="https://opencollective.com/react-router">~\$17k</a> of donations annually.</p>
<p>The root of the problem is that open-source donations are made on a per-project basis². To support a project via GitHub Sponsors or OpenCollective, you must create yet another auto-renewing monthly subscription for each project you want to support. Moreover, at the moment of truth (<em>"I'm gonna sponsor X!"</em>) it's easy to "logic" yourself out of donating (<em>"But what if it gets replaced by some new hotness next week?!"</em>). This has a huge dampening effect on total donations to open source. And in the end, only the projects that are massively, insanely, indisputably useful get funded. And those projects are usually the "big stuff" — frameworks, self-hostable software, etc.</p>
<p>A new model is needed; one that works for the small- and medium-sized projects, not just the huge ones. So I propose a novel(-ish) approach to OSS sustainability.</p>
<h2 id="introducingsponsorpools">Introducing "sponsor pools"</h2>
<ol>
<li>Every month, you donate some amount into a "wallet".</li>
<li>Your funds are then distributed to the projects in your "sponsor pool". Your sponsor pool is just the set of open-source projects you want to support.</li>
<li>Adding new projects to your pool should require <strong>one click</strong> —&nbsp;as easy as starring the repo on GitHub.</li>
</ol>
<p>That's it. It's hardly ingenious, which is why it's surprising that no major player in OSS has implemented it for facilitating open source donations.³</p>
<p>This would achieve the holy grail of OSS sustainability: the one-click sponsorship.⁴ Once a person has funded their sponsor pool, it would take <em>a single click</em> to financially support another project. <mark>The marginal cost — both psychological and financial — of supporting additional projects would drop to zero.</mark> That's a game changer.</p>
<h3 id="whythisworksbetter">Why this works better</h3>
<p>Why do I think this will dramatically increase the total amount donated to open source? To answer that, consider a hypothetical question.</p>
<p><em>How much of your income would you be willing to donate to open-source software every year?</em></p>
<p>I suspect the typical overpaid HN lurker reading this post (👋) came up with a number in the hundreds of dollars. Compare that with how much you’re actually donating — is there a difference? There is for me. <mark>That's because there's currently no way to make a donation to the abstract concept of "open source software".</mark> But with sponsor pools, donation amounts will reflect an honest answer to the question posed above.</p>
<h3 id="github">GitHub</h3>
<p>The best case scenario, in my opinion, is for GitHub to natively support this model as an extension of GitHub Sponsors. It's where most projects live so it's best positioned to create a zero-friction donation system like this.</p>
<p>Of course, if GitHub implemented something like this, they would likely divorce the donation mechanism from stars. Perhaps instead, users who have created and funded a sponsor pool can be presented with an "Add to Pool" button in place of the current "Sponsor" button.</p>
<p><img src="https://colinhacks.com/add_to_pool.png" alt="Add to pool button" /></p>
<p>GitHub has a financial incentive to switch to this approach — sort of. They're currently eating the cost of all processing fees for transactions on GitHub Sponsors. So if you sponsor a project at $1/mo, the maintainer gets $1/mo…and GitHub pays \$0.30 to the credit card companies. Larger donation sizes means GitHub pays a proportionally smaller amount as fees.</p>
<p>Note that I said <em>proportionally smaller</em>. If more total donations are being processed (which is the goal of this proposal!), they may still end up paying more in total fees, even if the ratio of fees-to-donations is smaller. If the sponsor pool concept is massively successful —&nbsp;say, cumulative donations of $1B annually — GitHub will be eating nearly $20M in card fees. I suspect that would ruffle some feathers at Microsoft.</p>
<p>As a final addendum —&nbsp;I'd love to see embeddable "badges" that are unlocked at different levels of donorship.&nbsp;Imagine a world where you see a "GitHub Sponsor Gold Badge" in the footer of someone’s website, indicating that they donate, say, \$1000+ annually to open source. Clicking on this badge would bring you to a trusted site that lets you verify the claim. Here's my quick-and-dirty attempt at a badge design:</p>
<p><img src="https://colinhacks.com/gold_octocat.jpg" alt="order of the gold octocat" /></p>
<p>Some may cry "virtue signaling" but approaches like this are a proven way to establish positive reinforcement loops that a) increase awareness and b) encourage more folks to donate to open source.</p>
<h2 id="wrappingup">Wrapping up</h2>
<p>This is a hard problem with a vast array of potential solutions. I don't mean to criticize any existing approaches; they've all done a lot for maintainers, including myself, and I don't mean to minimize that. There may also be major implementational or regulatory hurdles to building something like this that I'm not aware of. This is merely a hypothetical exercise.</p>
<p><em>I write proposals like this one every month or so. Put your email in the box to get future proposals delivered to your inbox. ✌️</em></p>
<!-- signupfield -->
<!-- _Alternatively follow me [on Twitter](https://twitter.com/colinhacks) or subscribe to [the RSS feed](https://colinhacks.com/rss.xml)._ -->
<h3 id="footnotes">Footnotes</h3>
<p>¹ Some examples of financially successful large projects.</p>
<p>&gt;</p>
<ul>
<li>operating systems: Red Hat, Elementary OS</li>
<li>frameworks: Livewire, Next.js</li>
<li>CMSs: Ghost, Wordpress</li>
<li>Full-featured applications: Sentry, Blender, Discourse</li>
</ul>
<p><br></p>
<p>² It's worth noting that currently GitHub Sponsorships actually correspond to a given user or organization, NOT a particular repository or project.</p>
<blockquote>
  <p>I think it's important that the sponsorship pools themselves are comprised of projects, <em>not</em> users or organizations. This gets complicated if multiple entities/organizations are listed as the maintainers of a particular project. There would need to be a mechanism in place to split the incoming donation money among the set of maintainers as they see fit.</p>
</blockquote>
<p><br></p>
<p>³ It's been pointed out to me that <a href="https://flattr.com">Flattr</a> uses this exact model! Unfortunately they're not intended for open source software specifically and simply don't have enough awareness to really move the needle. For this model to reach it's potential, it needs to be implemented by a visible, trusted, established player.</p>
<blockquote>
  <p>Other organizations trying interesting things: <a href="https://snowdrift.coop">Snowdrift</a> ("crowdmatching"…very cool concept), <a href="https://flossbank.com/">Flossbank</a> (a Tidelift-like model), <a href="https://gitcoin.co/">Gitcoin</a>.</p>
</blockquote>
<p><br></p>
<p>⁴ The even holier grail: a zero click sponsorship! An example of this approach is <a href="https://brave.com/">Brave Browser's</a> token-based micropayments system. Brave lets you fund your "wallet" with various cryptocurrencies, which are then distributed to creators proportional to the time you spend on their site.</p>
<blockquote>
  <p>It’s a great idea, but surveillance is necessary to gather the requisite information. This is implemented ethically by Brave, but that’s only possible because they control the full browser experience. For this to work across browsers would require an extension with full access to your browsing patterns. So probably a non-starter.</p>
</blockquote>]]></description><link>https://colinhacks.com/essays/a-new-funding-model-for-open-source-software</link><guid isPermaLink="false">/essays/a-proposal-for-open-source-sustainability</guid><category><![CDATA[Proposals]]></category><category><![CDATA[Open Source Sustainability]]></category><dc:creator><![CDATA[Colin McDonnell]]></dc:creator><pubDate>Wed, 29 Jul 2020 09:43:15 GMT</pubDate></item><item><title><![CDATA[Why you shouldn't use @emotion/core]]></title><description><![CDATA[<blockquote>
  <p>A maintainer of Emotion published a very thoughtful response to a version of this post! I encourage you to check that out <a href="https://github.com/emotion-js/emotion/issues/1883#issuecomment-637827074">here</a>. The essay below has modified to address some of his points.</p>
</blockquote>
<p>Emotion is my favorite CSS-in-JS library.</p>
<p>It's easy to define style classes — both inline or in separate files. You can powerfully compose classes in powerful ways with the <code>cx</code> utility (the Emotion equivalent of Jed Watson's <a href="https://github.com/JedWatson/classnames">classnames</a>). You sprinkle in your styles using the standard <code>className</code> attribute. There's no need to modify your markup/JSX — as it should be! You only need to install a single module (<code>yarn add emotion</code>). And there's no complicated Babel plugin or config file to set up.</p>
<pre><code>import { css, cs } from 'emotion';

const redBorder = css({ border: '1px solid red' });
const blueText = css({ color: 'blue' });

const MyComp = () =&gt; {
  return &lt;div className={cx(redBorder, blueText)}&gt;hello&lt;/div&gt;;
};
</code></pre>
<p>I'm currently building a Tailwind-style CSS-in-JS utility styling library with a maniacal focus on brevity and developer experience. If you want to hear about that when it's ready, join my mailing list.</p>
<!-- newsletterbutton -->
<h3 id="thebut">The "but"</h3>
<p>But unfortunately everything I just said only applies to the "vanilla" <code>emotion</code> module (<a href="https://emotion.sh/docs/emotion">docs</a>), <em>not</em> the confusingly-named <code>@emotion/core</code> module.</p>
<p><code>@emotion/core</code> is the React-centric Emotion wrapper that gives you some extra goodies, specifically server-side rendering and theming. It's also the official recommendation of the Emotion project for any React project.</p>
<p><img src="https://colinhacks.com/emotion_recommendation.png" alt="Emotion's recommending emotion core" /></p>
<p>So why exactly is Emotion recommending this for React devs?</p>
<h2 id="minimalupsides">Minimal upsides</h2>
<p>The main three advertised benefits of using <code>@emotion/core</code> are server-side rendering (SSR), theming, and customizability. Let's dig into those.</p>
<h3 id="outoftheboxssr">Out-of-the-box SSR</h3>
<p>There's no denying this is a remarkable technical achievement. Getting SSR to "just work" with both Next.js, Gatsby, and the classic <code>ReactDOMServer.renderToString</code> approach is very impressive. I'd be lying if I claimed to understand the complexities involved.</p>
<p>I don't have data on this, but — in my experience — SSR isn't a consideration for a hefty majority of React projects. If you started a project/website in the last 7 years where SEO/SEO/pageload speed/bundle size was an important design consideration, you probably didn't choose React. Website builders, static site generators, and HTML templating still dominate that arena. Take it from someone who got <a href="https://news.ycombinator.com/item?id=23309002">torn apart on HN</a> for advocating the use of React/Next.js for personal developer websites 😘</p>
<!-- I appreciate the technical difficulty of getting SSR to work out of the box; it's a hard problem. I _don't_ appreciate that Emotion tries to drive people towards `@emotion/core` instead of telling them how insanely easy it is to set up SSR themselves with vanilla `emotion`. -->
<p>For the people who do need SSR, the guidance is a little slim.</p>
<h4 id="nextjs">Next.js</h4>
<p>There's no explicit documentation from Next.js on how to set up SSR with vanilla <code>emotion</code>. Next.js provides a sample project <a href="https://github.com/vercel/next.js/tree/master/examples/with-emotion">here</a>. Notably, this project a) has a very uninformative Readme and b) is built with <code>@emotion/core</code>! So it's not immediately obvious that the approaches on display will even transfer to a vanilla project.</p>
<p>Enough buildup. Here's the internet's first comprehensive guide to setting up SSR with vanilla Emotion and Next.js:</p>
<ol>
<li><code>yarn add emotion-server</code></li>
<li>create <code>_document.tsx</code> in your <code>pages</code> directory and copy <a href="https://gist.github.com/colinhacks/c40519a6a050a99091862319151377ec">this gist</a> into it. ⚠️ This gist is intended for Emotion v10. Version 11 is the latest version. ⚠️</li>
<li>ok ur done</li>
</ol>
<h4 id="gatsby">Gatsby</h4>
<p>For completeness, here's some instructions for Gatsby users, too.</p>
<ol>
<li><code>yarn add gatsby-plugin-emotion</code></li>
<li>add <code>'gatsby-plugin-emotion'</code> to your <code>plugins</code> list in <code>gatsby-config.js</code></li>
</ol>
<p>If you're using @emotion/core to avoid the complexities of SSR configuration, you may want to reconsider.</p>
<h3 id="theming">Theming</h3>
<p>In the era of React Context and Hooks, there's no reason for libraries to use prop or high-order components to handle theming. Emotion provides a useTheme hook, but it still requires adding an additional library (<code>emotion-theming</code>).</p>
<p>This isn't a controversial claim; the next version of Emotion <a href="https://github.com/emotion-js/emotion/pull/973/files">will explicitly recommend</a> using a Context/Hook based solution, so I won't belabor this point.</p>
<!-- Touting this as a feature is a little silly. The point of CSS-in-JS is to define our styles as a data structure in a high-level programming language. Your styles are code; you can use the full might of JavaScript to define/modify/generate your styles; that's the whole appeal.  -->
<!-- Of course, if you theme doesn't change dynamically (and is it really a theme if it does?) just define your theme properties as variables and use import them into your components as needed. This is part of the appeal of CSS-in-JS — it lets you treat your styles just like any other code. If you're using TypeScript here's some code to get you started: -->
<p>Even Context/Hooks might be overkill for many projects. Just define your theme values as variables and import them into components as needed. If you're using TypeScript here's some code to get you started:</p>
<pre><code class="tsx language-tsx">// theme.ts
export const primaryColor = "blue";
export const serif = `'Encode Sans', Times New Roman, Times, serif`;

// anydamnfile.ts
import { css } from 'emotion';
import * as theme from './theme.ts';

export const MyComponent = ()=&gt;{
  return &lt;p className={css({ color: theme.primaryColor, fontFamily: theme.serif })}&gt;
}
</code></pre>
<p>If you want to import your theme with a <code>useTheme</code> hook, here's a workable implementation that took me several seconds to write:</p>
<pre><code class="tsx language-tsx">import * as theme from './theme.ts';
export const useTheme = () =&gt; theme;
</code></pre>
<h3 id="customization">Customization</h3>
<blockquote>
  <p>Edit: This section was added after the fact to address a concern raised by the maintainer of Emotion in <a href="https://github.com/emotion-js/emotion/issues/1883#issuecomment-637827074">his response</a> to an earlier version of this post.</p>
</blockquote>
<p>@emotion/core provides a <code>CacheProvider</code> component that lets you customize low-level aspects of it's behavior. This customization isn't possible with vanilla emotion. I'll let the maintainer of Emotion explain it:</p>
<blockquote>
  <p>Because [@emotion/core] doesn't give you string class names back, but rather just opaque objects, the rule insertion is lazy. This might not be as interesting on its own, but it allows us to customize injection through <code>&lt;CacheProvider&gt;</code>. Use cases for this are not as common - but they exist and once you ever come across one then you might be very thankful for the path we've chosen…Main things that can be customized:</p>
  <ul>
  <li>prefixing rules</li>
  <li>parser plugins</li>
  <li>container element (very important for rendering into iframes)</li>
  <li>nonce (very important for CSP)</li>
  </ul>
  <p>— Andarist (<a href="https://github.com/Andarist">Github</a>)</p>
</blockquote>
<p>If you absolutely need this degree of customizability, then @emotion/core is probably right for you.</p>
<p>For everyone else, let's look at the downsides.</p>
<h2 id="seriousdownsides">Serious downsides</h2>
<h3 id="thecssprop">The <code>css</code> prop</h3>
<p>Emotion <a href="https://emotion.sh/docs/css-prop">recommends using</a> their non-standard <code>css</code> prop to style your components, instead of React's built-in <code>className</code>. This causes me immeasurable emotional pain.</p>
<!-- For starters, supporting a non-native prop requires Emotion to entirely hijack React's JSX parsing, which seems messy and overkill. -->
<p>This approach destroys the portability of your React components. Your components are now unusable in any codebase that isn't configured to use <code>@emotion/core</code>.</p>
<p>The portability and encapsulation of React components is one of the most powerful and wondrous achievements in web development in the last last decade. Don't give that up without a good reason!</p>
<h3 id="installationwoes">Installation woes</h3>
<p>Unfortunately to get that non-native <code>css</code> prop to work, Emotion core entirely replaces your project's JSX parser. It substitutes the built-in <code>React.createElement</code> function with Emotion's custom <code>jsx</code> function.</p>
<p><img src="https://colinhacks.com/jsx_replacement.png" alt="Replacing React's createElement function" /></p>
<p>There are a couple ways to set this up.</p>
<p>Option #1: install the <code>@emotion/babel-preset-css-prop</code> Babel plugin and add it to your <code>.babelrc</code>. If you're using Create React App, this isn't impossible. If you're using TypeScript, you probably don't have a <code>.babelrc</code> in your project.</p>
<p>If you're in one of those buckets, there's option #2: copy these two lines at the top of every React component you want to style with Emotion:</p>
<pre><code class="tsx language-tsx">/** @jsx jsx */

import { jsx } from '@emotion/core';
</code></pre>
<!-- This is a pragma statement that tells Babel to use Emotion's `jsx` function to create React elements instead of React's built-in `React.createElement` function. -->
<!-- `@emotion/core` in every component file (just like you need `import React from 'react'` in every file containing JSX). -->
<p>If your TypeScript config or linter doesn't allow unused imports, you'll have to disable those rules to get rid of the warning. Check out <a href="https://github.com/emotion-js/emotion/issues/1046">this issue</a> if you want to see dozens of TypeScript users being sad about this.</p>
<p><img src="https://colinhacks.com/sad_emotion_small.png" alt="Sad Typescripters with linter warnings" /></p>
<h3 id="lackofcomposability">Lack of composability</h3>
<p>Perhaps the most damning issue with @emotion/core is that it makes the simple things harder. If you want to define a new class or use <code>cx</code>, you have to wrap your component with the <code>ClassNames</code> render prop. But with @emotion/core, these basic functions — found in nearly all CSS-in-JS libraries — requires you to modify your markup. In my humble opinion, requiring markup modifications is a cardinal sin for a styling library.</p>
<p>Here's the example from the top of this post, reimplemented with @emotion/core:</p>
<pre><code class="tsx language-tsx">import { ClassNames } from '@emotion/core';

const MyComp = () =&gt; {
  return (
    &lt;ClassNames&gt;
      {({ css, cx }) =&gt; {
        const redBorder = css({ border: '1px solid red' });
        const blueText = css({ color: 'blue' });

        return &lt;div className={cs(redBorder, blueText)}&gt;hello&lt;/div&gt;;
      }}
    &lt;/ClassNames&gt;
  );
};
</code></pre>
<h2 id="wrappingup">Wrapping up</h2>
<p>I understand how this happened. Vanilla emotion was undoubtedly getting flooded with GitHub issues by frustrated developers hitting against subtle limitations of its design. @emotion/core fixes these issues. But because @emotion/core is now the officially recommended approach for <em>all</em> React projects (the vanilla option isn't even mentioned on the Readme anymore), I suspect thousands of developers using it who would be better served by plain ol' <code>emotion</code>.</p>
<p>And finally: a huge thank you to the Emotion team for all their exceptional work and contributions to the open-source community.</p>
<p><em>I'm an open sourcer and ride-or-die React fan. Put your email in the box to hear when I write new things.</em></p>
<!-- signupfield -->]]></description><link>https://colinhacks.com/essays/emotion-core-vs-vanilla-emotion</link><guid isPermaLink="false">/essays/emotion-core-vs-vanilla-emotion</guid><category><![CDATA[Jamstack]]></category><category><![CDATA[React]]></category><category><![CDATA[UI Design]]></category><dc:creator><![CDATA[Colin McDonnell]]></dc:creator><pubDate>Sun, 07 Jun 2020 02:27:58 GMT</pubDate></item><item><title><![CDATA[Choosing a tech stack for my personal dev blog in 2020]]></title><description><![CDATA[<blockquote>
  <p>Check out the HN <del>roast</del> discussion <a href="https://news.ycombinator.com/item?id=23309002">here</a>! 🤗</p>
  <p>This article has been <a href="https://howtorecover.me/vybor-tekhnicheskogo-steka">translated into Russian</a>.</p>
</blockquote>
<p>I recently set out to build my personal website — the one you're reading now, as it happens!</p>
<p>Surprisingly, it was harder than expected assemble a "tech stack" that met my criteria. My criteria are pretty straightforward; I would expect most React devs to have a similar list. Yet it was surprisingly hard to put all these pieces together.</p>
<p>Given the lack of a decent out-of-the-box solution, I worry that many developers are settling for static-site generators that place limits on the interactivity and flexibility of your website. We can do better.</p>
<div style="padding:14px 20px; border: 0px solid #555; margin:15px 0px; border-left: 4px solid #4BBAAB">
  <b>tl;dr</b> 
  <br/>
  Clone the repo here to get started with this setup: <a href="https://github.com/colinhacks/devii">https://github.com/colinhacks/devii</a>.
</div>
<p>Let's quickly run through my list of design goals:</p>
<h3 id="reacttypescript">React (+ TypeScript)</h3>
<p>I want to build the site in React and TypeScript. I love them both wholeheartedly, I use them for my day job, and they're gonna be around for a long time. Plus writing untyped JS makes me feel dirty.</p>
<p>I don't want limitations on what my personal website can be/become. Sure, at present my site consists of two simple, static blog posts. But down the road, I may want to build a page that contains an interactive visualization, a filterable table, or a demo of a React component I'm open-sourcing. Even something simple (like the email newsletter signup form at the bottom of this page) was much more pleasant to implement in React; how did we use to build forms again?</p>
<p>Plus: I want access to the npm ecosystem and my favorite UI, animation, and styling libraries. I sincerely hope I never write another line of raw CSS ever again.</p>
<h3 id="goodauthoringexperience">Good authoring experience</h3>
<p>If it's annoying to write a blog post, I won't do it. That's a regrettable law of the universe. Even writing blog posts in plain HTML — a bunch of <code>&lt;p&gt;</code> tags in a div — is just annoying enough to bug me. The answer: Markdown of course!</p>
<p>Static site generators (SSGs) like Hugo and Jekyll provide an undeniably wonderful authoring experience. All you have to do is <code>touch</code> a new .md file in the proper directory and get to writing. Unfortunately all Markdown-based SSGs I know of are too restrictive. Mixing React and Markdown on the same page is either impossible or tricky. If it's possible, it likely requires some plugin/module/extension, config file, blob of boilerplate, or egregious hack. Sorry Hugo, I'm not going to re-write my React code using <code>React.createElement</code> like it's 2015.</p>
<p>Well, that doesn't work for me. I want my website to be React-first, with a sprinkling of Markdown when it makes my life easier.</p>
<h3 id="staticgeneration">Static generation</h3>
<p>As much as I love the Jamstack, it doesn't cut it from an SEO perspective. Many blogs powered by a "headless CMS" require two round trips before rendering the blog content (one to fetch the static JS bundle and another to fetch the blog content from a CMS). This degrades page load speeds and user experience, which accordingly degrades your rankings on Google.</p>
<p>Instead I want every page of my site to be pre-rendered to a set of fully static assets, so I can deploy them to a CDN and get fast page loads everywhere. You could get the same benefits with server-side rendering, but that requires an actual server and worldwide load balancing to achieve comparable page load speeds. I love overengineering things as much as the next guy, even I have a line. 😅</p>
<h2 id="mysolution">My solution</h2>
<p>I describe my final architecture design below, along with my rationale for each choice. I distilled this setup into a website starter/boilerplate available here: https://github.com/colinhacks/devii. Below, I allude to certain files/functions I implemented; to see the source code of these, just clone the repo <code>git clone git@github.com:colinhacks/devii.git</code></p>
<h3 id="nextjs">Next.js</h3>
<p>I chose to build my site with Next.js. This won't be a surprising decision to anyone who's played with statically-rendered or server-side rendered React in recent years. Next.js is quickly eating everyone else's lunch in this market, especially Gatsby's (sorry Gatsby fans).</p>
<p>Next.js is by far the most elegant way (for now) to do any static generation or server-side rendering with React. They released their next-generation (pun intended) static site generator in the <a href="https://nextjs.org/blog/next-9-3">9.3 release</a> back in March 2020. In the spirit of using technologies <a href="https://www.youtube.com/watch?v=eBAX8MbRYFA">in the spring of their life</a>, Next.js is a no-brainer.</p>
<p>Here's a quick breakdown of the project structure. No need to understand every piece of it; but it may be useful to refer to throughout the rest of this post.</p>
<pre><code>.
├── README.md
├── public // all static files (images, etc) go here
├── pages // every .tsx component in this dir becomes a page of the final site
|   ├── index.tsx // the home page (which has access to the list of all blog posts)
|   ├── blog
|       ├── [blog].md // a template component that renders the blog posts under `/md/blog`
├── md
|   ├── blog
|       ├── devii.md // this page!
        ├── whatever.md // every MD file in this directory becomes a blog post
├── components
|   ├── Code.tsx
|   ├── Markdown.tsx
|   ├── &lt;various others&gt;
├── loader.ts // contains utility functions for loading/parsing Markdown
├── node_modules
├── tsconfig.json
├── package.json
├── next.config.js
├── next-env.d.ts
├── .gitignore
</code></pre>
<!-- Check out the Next.js documentation [here](https://nextjs.org/docs) to make sure it's the right choice for your project. -->
<h3 id="typescriptreact">TypeScript + React</h3>
<p>Both React and TypeScript are baked into the DNA of Next.js, so you get these for free when you set up a Next.js project.</p>
<p>Gatsby, on the other hand, has a special plugin for TypeScript support, but it's not officially supported and seems to be <a href="https://github.com/gatsbyjs/gatsby/issues/18983">low on their priority list</a>. Also, after messing with it for an hour I couldn't get it to play nice with hot reload.</p>
<h3 id="markdownauthoring">Markdown authoring</h3>
<p>Using Next's special <code>getStaticProps</code> hook and glorious <a href="https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr">dynamic imports</a>, it's trivial to import a Markdown file and pass its contents into your React components as a prop. This achieves the holy grail I was searching for: the ability to easily mix React and Markdown.</p>
<h4 id="frontmattersupport">Frontmatter support</h4>
<p>Every Markdown file can include a "frontmatter block" containing metadata. I implemented a simple utility function (<code>loadPost</code>) that loads a Markdown file, parses its contents, and returns a TypeScript object with the following signature:</p>
<pre><code class="ts language-ts">type PostData = {
  path: string; // the relative URL to this page, can be used as an href
  content: string; // the body of the MD file
  title?: string;
  subtitle?: string;
  date?: number;
  author?: string;
  author_image?: string;
  tags?: string[];
  banner_image?: string;
  thumb_image?: string;
};
</code></pre>
<p>I implemented a separate function <code>loadPosts</code> that loads <em>all</em> the Markdown files under <code>/md/blog</code> and returns them as an array (<code>PostData[]</code>). I use <code>loadPosts</code> on this site's home page to render a list of all posts I've written.</p>
<h3 id="mediuminspireddesign">Medium-inspired design</h3>
<p>I used the wonderful <a href="https://github.com/rexxars/react-markdown"><code>react-markdown</code></a> package to render Markdown as a React component. My Markdown rendered component (<code>/components/Markdown.tsx</code>) provides some default styles inspired by Medium's design. Just modify the <code>style</code> pros in <code>Markdown.tsx</code> to customize the design to your liking.</p>
<h3 id="githubstylecodeblocks">GitHub-style code blocks</h3>
<p>You can easily drop code blocks into your blog posts using triple-backtick syntax. Specify the programming language with a "language tag", <a href="https://help.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks">just like GitHub</a>!</p>
<p>To achieve this I implemented a custom <code>code</code> renderer (<code>/components/Code.tsx</code>) for <code>react-markdown</code> that uses <a href="https://github.com/conorhastings/react-syntax-highlighter#readme">react-syntax-highlighter</a> to handle the highlighting. So this:</p>
<!-- I landed on this solution after wasting hours playing with other options. CodeMirror has bad React support (the only React wrapper for it is inauspiciously named `react-codemirror2`) and [bizarre selection issues](https://github.com/codemirror/CodeMirror/issues/1099) for `readonly` code blocks. The popular `highlight.js` project requires you to [initialize the library](https://github.com/highlightjs/highlight.js/issues/925) in `componentDidMount` like its 2015 :/  -->
<pre><pre><code class="ts language-ts">// pretty neat huh?
const test = (arg: string) =&gt; {
  return arg.length &gt; 5;
};
</code></pre></pre>
<p>turns into this:</p>
<pre><code class="ts language-ts">// pretty neat huh?
const test = (arg: string) =&gt; {
  return arg.length &gt; 5;
};
</code></pre>
<h3 id="rssfeedgeneration">RSS feed generation</h3>
<p>An RSS feed is auto-generated from your blog post feed. This feed is generated using the <code>rss</code> module (for converting JSON to RSS format) and <code>showdown</code> for converting the markdown files to RSS-compatible HTML. The feed is generated during the build step and written as a static file to <code>/rss.xml</code> in your static assets folder. It's dead simple. That's the joy of being able to easily write custom build scripts on top of Next.js's <code>getStaticProps</code> hooks!</p>
<h3 id="seo">SEO</h3>
<p>Every blog post page automatically populated meta tags based on the post metadata. This includes a <code>title</code> tag, <code>meta</code> tags, <code>og:</code> tags, Twitter metadata, and a <code>link</code> tag containing the canonical URL. You can modify/augment this in the <code>PostMeta.ts</code> component.</p>
<h3 id="staticgeneration-1">Static generation</h3>
<p>You can generate a fully static version of your site using <code>yarn build &amp;&amp; yarn export</code>. This step is entirely powered by Next.js. The static site is exported to the <code>out</code> directory.</p>
<p>After its generated, use your static file hosting service of choice (Firebase Hosting, Vercel, Netlify) to deploy your site.</p>
<h3 id="insanelycustomizable">Insanely customizable</h3>
<p>There's nothing "under the hood" here. You can view and modify all the files that provide the functionality described above. Devii just provides a project scaffold, some Markdown-loading loading utilities (in <code>loader.ts</code>), and some sensible styling defaults (especially in <code>Markdown.tsx</code>).</p>
<p>To start customizing, modify <code>index.tsx</code> (the home page), <code>BlogPost.tsx</code> (the blog post template), and <code>Markdown.tsx</code> (the Markdown renderer).</p>
<h2 id="getstarted">Get started</h2>
<p>Head to the GitHub repo to get started: <a href="https://github.com/colinhacks/devii">https://github.com/colinhacks/devii</a>. If you like this project, leave a ⭐️star⭐️ to help more people find Devii! 😎</p>
<p>To jump straight into the code, clone the repo and start the development server like so:</p>
<pre><code class="sh language-sh">git clone git@github.com:colinhacks/devii.git mysite
cd mysite
yarn
yarn dev
</code></pre>
<p><em>I write essays like this one every month. Put your email in the box to get future proposals delivered to your inbox.</em></p>
<!-- signupfield -->
<!-- _Alternatively follow me [on Twitter](https://twitter.com/colinhacks) or subscribe to [the RSS feed](https://colinhacks.com/rss.xml)._ -->]]></description><link>https://colinhacks.com/essays/devii</link><guid isPermaLink="false">/essays/devii</guid><category><![CDATA[Jamstack]]></category><category><![CDATA[React]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[Colin McDonnell]]></dc:creator><pubDate>Tue, 26 May 2020 03:18:56 GMT</pubDate></item><item><title><![CDATA[Designing the perfect Typescript schema validation library]]></title><description><![CDATA[<blockquote>
  <p><strong>Update 2023</strong> — Many of the problems with other libraries have been solved since this post was originally published.</p>
</blockquote>
<p>There are a handful of wildly popular validation libraries in the Javascript ecosystem with thousands of stars on GitHub.</p>
<p>When I started my quest to find the ideal schema declaration/validation library, I assumed the hardest part would be identifying the Most Great option from a sea of excellent ones.</p>
<p>But as I dug deeper into the many beloved tools in the ecosystem, I was surprised that none of them could provide the features and developer experience I was looking for.</p>
<blockquote>
  <p><strong>tl;dr</strong>: I made a new Typescript validation library that has static type inference and the best DX this side of the Mississippi. To jump straight to README, head over to https://github.com/colinhacks/zod</p>
</blockquote>
<h3 id="apinchofcontext">A pinch of context</h3>
<p>I'm building an API for a healthcare application with Typescript. Naturally I want this mission-critical medical software to be rock-solid, so my goal is to build a fully end-to-end typesafe data layer.</p>
<p>All data that passes from the client to the server AND server to client should be validated at runtime. Moreover, both the client and server should have static type definitions of all payloads, so I can catch more errors at compile-time.</p>
<p>I also don't hate myself, so I don't want to keep my static types and runtime type validators in sync by hand as my data model changes. Which means we need a tool that supports 🚀StAtIc TyPe InFeReNcE!!🚀 <em>vuvuzela sounds</em></p>
<p>Let's look at our options!</p>
<h2 id="theexistingoptions">The existing options</h2>
<h3 id="ahrefhttpsgithubcomhapijsjoijoia143knbspstars"><a href="https://github.com/hapijs/joi">Joi</a> (14.3k&nbsp;stars)</h3>
<p>Doesn't support static type inference. Boo.</p>
<h3 id="ahrefhttpsgithubcomjquenseyupyupa86knbspstars"><a href="https://github.com/jquense/yup">Yup</a> (8.6k&nbsp;stars)</h3>
<p>Yup is jquense's popular, Joi-inspired validation library that was implemented first in vanilla JS, with Typescript typings added later.</p>
<p>Yup supports static type inference! But with a small caveat: the typings are wrong.</p>
<p>For instance, the yup package treats all object properties as optional by default.</p>
<pre><code class="ts language-ts">const schema = yup.object({
  asdf: yup.string(),
});
schema.validate({}); // passes
</code></pre>
<p>Yet the inferred type indicates that all properties are required. 😕</p>
<pre><code class="ts language-ts">type SchemaType = yup.InferType&lt;typeof schema&gt;;

// returns { asdf: string }
// should be { asdf?: string }
</code></pre>
<p>Yup also mis-infers the type of "required" arrays.</p>
<pre><code class="ts language-ts">const numList = yup.array().of(yup.string()).required();

// .required() is used to indicate a non-empty list
numList.validateSync([]); // fails

// yet the inferred type doesn't reflect this
type NumList = yup.InferType&lt;typeof numList&gt;;
// returns string[]
// should be [string,...string[]]
</code></pre>
<p>Finally, Yup doesn't explicitly support generic union and intersection types. This is a recurring <a href="https://github.com/jquense/yup/issues/593">source</a> of <a href="https://github.com/jquense/yup/issues/321">frustration</a> for the Yup community.</p>
<p>These may sound like nitpicks. But it's very uncool for the inferred type to not actually reflect the actual type of the validator it came from.</p>
<p>Moreover, there are no plans to fix these issues in Yup's type inference because those changes would be backwards compatible. See more about this <a href="https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42360">here</a>.</p>
<h3 id="ahrefhttpsgithubcomgcantiiotsiotsa27knbspstars"><a href="https://github.com/gcanti/io-ts">io-ts</a> (2.7k&nbsp;stars)</h3>
<p>Finally, a library that was designed for Typescript from the ground up!</p>
<p>Look, <code>io-ts</code> is excellent library. It's creator, gcanti, has done more than anyone to bring proper higher-order functional programming to Typescript with his <code>fp-ts</code> library.</p>
<p>But in my situation, and I think many others', <code>io-ts</code> prioritizes functional programming purity at the expense of developer experience. Functional purity is a valid and admirable design goal, but it makes <code>io-ts</code> particularly hard to integrate into an existing codebase with a more procedural or object-oriented bias. It's difficult to progressively integrate <code>io-ts</code> without entirely refactoring your code with a more functional flavor.</p>
<p>For instance, consider how to define an object schema with optional properties in <code>io-ts</code>:</p>
<pre><code class="ts language-ts">const A = t.type({
  foo: t.string,
});

const B = t.partial({
  bar: t.number,
});

const C = t.intersection([A, B]);

type C = t.TypeOf&lt;typeof C&gt;;
/*
{
  foo: string;
  bar?: number | undefined
}
*/
</code></pre>
<p>You must define the required props with a call to <code>t.type({...})</code>, define your optional props with a call to <code>t.partial({...})</code>, and merge them with <code>t.intersection()</code>.</p>
<p>Spoiler alert: Here's the equivalent in my new library:</p>
<pre><code class="ts language-ts">const C = z.object({
  foo: z.string(),
  bar: z.number().optional(),
});

type C = t.TypeOf&lt;typeof C&gt;;
/* {
  foo: string;
  bar?: number | undefined
} */
</code></pre>
<p><code>io-ts</code> also requires the use of gcanti's functional programming library <code>fp-ts</code> to parse results and handle errors. From the <code>io-ts</code> docs, here is the most basic example of how to run a validation:</p>
<pre><code class="ts language-ts">import * as t from 'io-ts';
import {pipe} from 'fp-ts/lib/pipeable';
import {fold} from 'fp-ts/lib/Either';

// failure handler
const onLeft = (errors: t.Errors): string =&gt; `${errors.length} error(s) found`;

// success handler
const onRight = (s: string) =&gt; `No errors: ${s}`;

pipe(t.string.decode('a string'), fold(onLeft, onRight));
// =&gt; "No errors: a string"
</code></pre>
<p>The functional approach here is alienating for developers who aren't accustomed to it.</p>
<p>Again: <code>fp-ts</code> is <em>fantastic</em> resource for developers looking to keep their codebase strictly functional. But depending on <code>fp-ts</code> necessarily comes with a lot of intellectual overhead; a developer has to be familiar with functional programming concepts, <code>fp-ts</code>'s nomenclature, and the Either monad to do a simple schema validation. It's alienating for devs who don't have a functional background, and it drives them to libraries like Yup, which returns incorrect types.</p>
<h2 id="introducingzod">Introducing Zod</h2>
<p>So, in the tradition of many a nitpicky programmer, I decided to build my own library from scratch. What could go wrong?</p>
<p>The final result: <a href="https://github.com/colinhacks/zod">https://github.com/colinhacks/zod</a>.</p>
<p>Zod is a validation library designed for optimal developer experience. It's a Typescript-first schema declaration library with rigorous (and correct!) inferred types, incredible developer experience, and a few killer features missing from the existing libraries.</p>
<ul>
<li>Uses Typescript generic inference to statically infer the types of your schemas</li>
<li>Eliminates the need to keep static types and runtime validators in sync by hand</li>
<li>Has a composable, declarative API that makes it easy to define complex types concisely</li>
</ul>
<p>Zod was also designed with some core principles designed to make all declarations as non-magical and developer-friendly as possible:</p>
<ul>
<li>Fields are required unless explicitly marked as optional (just like Typescript!)</li>
<li>Schemas are immutable; methods (i.e. <code>.optional()</code>) return a new instance.</li>
<li>Zod schemas operate on a <a href="https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/">Parse, don't validate!</a> basis!</li>
</ul>
<blockquote>
  <p>To jump straight to README, head over to https://github.com/colinhacks/zod. If you're feeling frisky, leave a star 🌟👍</p>
</blockquote>
<h3 id="primitives">Primitives</h3>
<pre><code class="ts language-ts">import * as z from 'zod';

const stringSchema = z.string(); // =&gt; ZodType&lt;string&gt;
const numberSchema = z.number(); // =&gt; ZodType&lt;number&gt;
const booleanSchema = z.boolean(); // =&gt; ZodType&lt;boolean&gt;
const undefinedSchema = z.undefined(); // =&gt; ZodType&lt;undefined&gt;
const nullTypeSchema = z.null(); // =&gt; ZodType&lt;null&gt;
</code></pre>
<h3 id="parsing">Parsing</h3>
<pre><code class="ts language-ts">// every ZodType instance has a .parse() method
const stringSchema = z.string();
stringSchema.parse('fish'); // =&gt; "fish"
stringSchema.parse(12); // throws Error('Non-string type: number');
</code></pre>
<h3 id="typeinference">Type inference</h3>
<p>Like in <code>io-ts</code>, you can extract the Typescript type of any schema with <code>{'z.TypeOf&lt;&gt;'}</code>.</p>
<pre><code class="ts language-ts">const A = z.string();
type A = z.TypeOf&lt;typeof A&gt;; // string

const u: A = 12; // TypeError
const u: A = 'asdf'; // compiles
</code></pre>
<p>We'll include examples of inferred types throughout the rest of the documentation.</p>
<h3 id="objects">Objects</h3>
<pre><code class="ts language-ts">// all properties are required by default
const dogSchema = z.object({
  name: z.string(),
  age: z.number(),
  neutered: z.boolean(),
});

type Dog = z.TypeOf&lt;typeof dogSchema&gt;;
/* equivalent to:
type Dog = {
  name:string;
  age: number;
  neutered: boolean;
} 
*/

const cujo = dogSchema.parse({
  name: 'Cujo',
  age: 4,
  neutered: true,
}); // passes, returns Dog

const fido: Dog = {
  name: 'Fido',
  age: 2,
}; // TypeError: missing required property 'neutered'
</code></pre>
<h3 id="arrays">Arrays</h3>
<pre><code class="ts language-ts">const dogsList = z.array(dogSchema);

dogsList.parse([{name: 'Cujo', age: 3, neutered: true}]); // passes
dogsList.parse([]); // passes
</code></pre>
<p>Plus you can explicitly define a non-empty array schema, something
<code>io-ts</code> doesn't support.</p>
<pre><code class="ts language-ts">// Non-empty lists

const nonEmptyDogsList = z.array(dogSchema).nonempty();
nonEmptyDogsList.parse([]); // throws Error("Array cannot be empty")
</code></pre>
<h3 id="unionsincludingnullableandoptionaltypes">Unions (including nullable and optional types)</h3>
<p>Zod includes a built-in <code>z.union</code> method for composing "OR"
types.</p>
<pre><code class="ts language-ts">const stringOrNumber = z.union([z.string(), z.number()]);

stringOrNumber.parse('foo'); // passes
stringOrNumber.parse(14); // passes
</code></pre>
<p>Unions are the basis for defining nullable and optional values.</p>
<pre><code class="ts language-ts">/* Optional Types */ // "optional string" === the union of string and undefined
const A = z.union([z.string(), z.undefined()]);
A.parse(undefined); // =&gt; passes, returns undefined
type A = z.TypeOf&lt;typeof A&gt;; // string | undefined
</code></pre>
<p>There is also a shorthand way to make a schema "optional":</p>
<pre><code class="ts language-ts">const B = z.string().optional(); // equivalent to A

const C = z.object({
  username: z.string().optional(),
});
type C = z.TypeOf&lt;typeof C&gt;; // { username?: string | undefined };
</code></pre>
<pre><code class="ts language-ts">/* Nullable Types */ const D = z.union([z.string(), z.null()]);

const E = z.string().nullable(); // equivalent to D
type E = z.TypeOf&lt;typeof D&gt;; // string | null
</code></pre>
<p>You can create unions of any two schemas.</p>
<pre><code class="ts language-ts">/* Custom Union Types */
const F = z.union([z.string(), z.number()]).optional().nullable();
F.parse('tuna'); // =&gt; tuna
F.parse(42); // =&gt; 42
F.parse(undefined); // =&gt; undefined
F.parse(null); // =&gt; null
F.parse({}); // =&gt; throws Error!

type F = z.TypeOf&lt;typeof F&gt;; // string | number | undefined | null;
</code></pre>
<h3 id="intersections">Intersections</h3>
<p>Intersections are useful for creating "logical AND" types.</p>
<pre><code class="ts language-ts">const a = z.union([z.number(), z.string()]);
const b = z.union([z.number(), z.boolean()]);

const c = z.intersection(a, b);
type c = z.TypeOf&lt;typeof C&gt;; // =&gt; number

const neverType = z.intersection(z.string(), z.number());
type Never = z.TypeOf&lt;typeof stringAndNumber&gt;; // =&gt; never
</code></pre>
<p>This is particularly useful for defining "schema mixins" that you can apply to multiple schemas.</p>
<pre><code class="ts language-ts">const HasId = z.object({
  id: z.string(),
});

const BaseTeacher = z.object({
  name: z.string(),
});

const Teacher = z.intersection(BaseTeacher, HasId);

type Teacher = z.TypeOf&lt;typeof Teacher&gt;;
// { id:string; name:string };
</code></pre>
<h3 id="objectmerging">Object merging</h3>
<p>In the examples above, the return value of <code>z.intersection</code> is an instance of <code>ZodIntersection</code>, a generic class that wraps the two schemas passed in as arguments.</p>
<p>But if you're trying to combine two object schemas, there is a shorthand:</p>
<pre><code class="ts language-ts">const Teacher = BaseTeacher.merge(HasId);
</code></pre>
<p>The benefit of using this shorthand is that the returned value is a new object schema (<code>ZodObject</code>), instead of a generic <code>ZodIntersection</code> instance. This way, you're able to fluently chain together many <code>.merge</code> calls:</p>
<pre><code class="ts language-ts">// chaining mixins
const Teacher = BaseTeacher.merge(HasId).merge(HasName).merge(HasAddress);
</code></pre>
<h3 id="tuples">Tuples</h3>
<p>These differ from arrays in that they have a fixed number of elements, and each element can have a different type.</p>
<pre><code class="ts language-ts">const athleteSchema = z.tuple([
  // takes an array of schemas
  z.string(), // name
  z.number(), // jersey number
  z.object({
    pointsScored: z.number(),
  }), // statistics
]);

type Athlete = z.TypeOf&lt;typeof athleteSchema&gt;;
// type Athlete = [string, number, { pointsScored: number }]
</code></pre>
<h3 id="recursivetypes">Recursive types</h3>
<p>You can define a recursive schema in Zod, but because of a limitation of Typescript, their type can't be statically inferred. If you need a recursive Zod schema you'll need to define the type definition manually, and provide it to Zod as a "type hint".</p>
<pre><code class="ts language-ts">interface Category {
  name: string;
  subcategories: Category[];
}

const Category: z.ZodType&lt;Category&gt; = z.lazy(() =&gt; {
  return z.object({
    name: z.string(),
    subcategories: z.array(Category),
  });
});

Category.parse({
  name: 'People',
  subcategories: [
    {
      name: 'Politicians',
      subcategories: [{name: 'Presidents', subcategories: []}],
    },
  ],
}); // passes
</code></pre>
<h3 id="functionschemas">Function schemas</h3>
<p>Zod also lets you define "function schemas". This makes it easy to validate the inputs and outputs of a function without intermixing your validation code and "business logic".</p>
<p>You can create a function schema with <code>z.function(args, returnType)</code> which accepts these arguments.</p>
<ul>
<li><code>args: ZodTuple</code> The first argument is a tuple (created with
<code>z.tuple(\[...\])</code> and defines the schema of the arguments to
your function. If the function doesn't accept arguments, you can pass an
empty tuple (<code>z.tuple([])</code>).</li>
<li><code>returnType: ZodType</code> The second argument is the function's return type. This can be any Zod schema.</li>
</ul>
<pre><code class="ts language-ts">const args = z.tuple([
  z.object({nameStartsWith: z.string()}),
  z.object({skip: z.number(), limit: z.number()}),
]);

const returnType = z.array(
  z.object({
    id: string(),
    name: string(),
  })
);

const FetcherFactory = z.function(args, returnType);
</code></pre>
<p><code>z.function</code> returns a higher-order "function factory". Every "factory" has <code>.validate()</code> method which accepts a function as input and returns a new function. The returned function automatically validates both its inputs and return value against the schemas provided to <code>z.function</code>. If either is invalid, the function throws.
This lets you confidently execute business logic in a "validated function" without worrying about invalid inputs or return types, mixing your validation and business logic, or writing duplicative types for your functions.
Here's an example.</p>
<pre><code class="ts language-ts">const validatedQueryUser = FetchFunction.validate((filters, pagination) =&gt; {
  // the arguments automatically have the appropriate types
  // as defined by the args tuple passed to `z.function()`
  // without needing to provide types in the function declaration

  filters.nameStartsWith; // autocompletes
  filters.ageLessThan; // TypeError

  // Typescript statically verifies that value returned by
  // this function is of type { id: string; name: string; }[]

  return 'salmon'; // TypeError
});

const users = validatedQueryUser(
  {
    nameStartsWith: 'John',
  },
  {
    skip: 0,
    limit: 20,
  }
); // =&gt; returns { id: string; name: string; }[]
</code></pre>
<p>This is particularly useful for defining HTTP or RPC endpoints that accept complex payloads that require validation. Moreover, you can define your endpoints once with Zod and share the code with both your client and server code to achieve end-to-end type safety.</p>
<h2 id="onwardsandupwards">Onwards and upwards</h2>
<p>I think there's a LOT of room for improvement in how we handle the complexity of data modeling, validation, and transport as developers. Typescript, GraphQL, and Prisma are huge steps towards a future where our tooling can provide guarantees of data integrity. But there's still a long way to go.</p>
<p>If you like what you see, head over to <a href="https://github.com/colinhacks/zod">https://github.com/colinhacks/zod</a> and leave a star.</p>
<p><em>I'm an open sourcer and ride-or-die TypeScripter. Put your email in the box to hear when I publish new essays or open source libraries.</em></p>
<!-- signupfield -->
<!-- _Alternatively follow me [on Twitter](https://twitter.com/colinhacks) or subscribe to [the RSS feed](https://colinhacks.com/rss.xml)._ -->]]></description><link>https://colinhacks.com/essays/zod</link><guid isPermaLink="false">/essays/zod</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Jamstack]]></category><dc:creator><![CDATA[Colin McDonnell]]></dc:creator><pubDate>Sun, 08 Mar 2020 08:00:00 GMT</pubDate></item></channel></rss>