The not-engineer's guide to building Technically

All the technical concepts we leaned on to build the web app

Last week, after 5 years of Substack, we migrated over to the app you're reading this on to give Technically readers a more navigable, Wikipedia-style reading experience.

Going from a blog on Substack to a standalone web app is a leap. There’s a lot of stuff that Substack takes care of for you, that we covered way back in 2020 when attempting to build a Substack clone.

No one at Technically is a software engineer by training (we’re actually both data people). Part of the reason to build Technically 2.0 is to show that technical concepts are knowable by anyone who’s handsome enough.

The TL;DR

As non-engineers, there were a handful of challenging technical pieces to Technically 2.0 that required a bit of grit to get through. Yes, even in the age of AI, we must still sweat.

In this post, we’ll use many of the concepts you’ve already read about, like APIs and databases , to walk through how we built the 4 most involved features of Technically 2.0:

  1. The reading list (bookmarks + progress tracking)
  2. The universe of concepts (tooltips + “mentioned terms" sections)
  3. Subscriptions and Stripe integration
  4. Team features (invites + organization subscriptions)

If you can understand how these 4 features work, you’re on your way to understand most of what’s in the software products you use every day.

Before we get in too deep here, there’s some stuff you should read (if you haven’t already) that will help all of this make more sense.

The Technically app is a web app, meaning it has a frontend and backend and runs in your browser. It’s hosted in the cloud , and uses a production database to store user and post data. It uses React to make interactions zippy. And we’ve used as much PaaS as possible to get this thing out there fast.

Without further ado…

The reading list

Without a reading list, you’d have no way to keep track of which articles to share with your team on Slack (“great post IMO") before you’ve actually read them yourself.

The reading list solves this by automatically tracking how you read Technically posts:

  • Bookmarks: A button you can use to save a post for later
  • In-progress articles: posts you’ve started but not finished
  • Completed articles: posts you’ve read all the way through

Frontends and backends

This interplay between the actual data – posts you’ve read, bookmarks you’ve made – and the display of that data is what people mean when they talk about frontends and backends. To quote from our post on the topic:

The best analogy I've thought of for frontends and backends is a restaurant. Say you're having a lovely evening with the homies and you stroll into your favorite upscale Brooklyn eatery. Your experience is interfacing with a few things: a nice table with comfortable chairs, high quality food and drink brought to your table, servers catering to your every whim.

Behind the scenes though, there's tons of logistical work that goes into being able to provide you with said experience. In particular, there's an entire kitchen staff that worries about what should be on the menu, how to cook everything consistently, where to source ingredients, etc.

What you see on the frontend

When you're reading an article on the Technically app, two things happen:

  1. Progress Tracking: When you first scroll down in a post, the app notices that and logs a “start” event in the database. Once you get towards the bottom of the page, it logs a “completion” event.

article title with progress tracking

  1. Bookmarking: You can click the bookmark button at the top of any article to save it for later.

technically reading list

Now that we have that data stored in the database, we can show it to users. Your reading list displays all of this activity back to you, by querying it from the app’s database:

These are built using HTML, CSS and some Javascript to change what’s displaying when you switch tabs.

🤔 Undefined term

Each of these elements (the bookmark button, the progress indicator, and the cards in the reading list for each post) could be called a component, which is a term you’ll hear often in web apps.

A component is a distinct combination of HTML (defining what content appears), CSS (how it looks) and Javascript (what interactions or data it handles). The innovation behind components is that you can reuse them on different pages across a web app, vs having to rebuild them in multiple places.

The reading list backend

The backend is more nebulous than the frontend—it’s everything that is not the frontend, everything in the app that you can’t interact with directly.

For the reading list this means a few pieces: database tables, functions to interact with those tables, and the API calls + authentication to make sure that interaction is secure.

Database tables

Behind the scenes, the app has two relational database tables to store your activity:

  1. View History: Records when you start reading an article and when you complete it
  2. Bookmarks: Stores which articles you've saved for later

React hooks

I mentioned earlier that the app is built in React – React is a frontend framework that helps developers built apps like ours with reusable, interactive components. There are special functions in React called "hooks" to log your reading progress and save bookmarks.

Hooks are basically smart sensors that can detect changes and respond to them, in this case by running an update in the database.

Hooks are like you giving your signature eye contact to the wait staff at a restaurant, to literally hook them into bringing you the dessert menu.

Database updates

In the app’s case, these hooks trigger updates in the database (the view history and bookmarks tables), based on how far you’ve scrolled on a page, or whether you’ve hit the bookmark button.

All of this happens through secure API calls to the production database (Supabase) that require authentication.

This ensures your reading list, however innocent, remains private, and that your neighbor (who also reads Technically I’ve heard) can’t see that you never finished reading about authentication.

Assembling the universe

In software (as in life), everything relates to everything else. It’s useless to understand backends without frontends.

For this reason technical content often reads like the dictionary when you’re 6 years old: using words I don’t understand to define other words I don’t understand.

The Technically universe of concepts solves this problem, by adding tooltips on key words and phrases:

a glossary tooltip on an undefined term

And summarizing these at the top of each post:

terms mentioned within a post

Each of these little cards are frontend React components (same as the reading list), but the data that feeds them isn’t your data, it’s the metadata within a post itself:

companies_within:  
 - aws  
 - databricks  
 - elastic  
 - mongodb  

terms_within:  
 - analytics  
 - api  
 - backend  
 - cloud  
 - data-lake  

Metadata is data about data (🤯). If you think of a post as data (text and images), these little annotations are data about the post; which companies it mentions and which terms are inside. Based on how this metadata is tagged, the app will query an articles table to fetch the descriptions of those terms and blurbs on what those companies do.

AI enters the chat

There are (checks notes) 112 posts in the Technically library, written over the course of 5 years, with ~100 defined terms in the Technically universe (fancy name for a glossary). Needless to say, there’s a lot of content to tag.

This is where AI shines, but not in exactly the way that you might think.

Uploading all Technically posts to Claude or ChatGPT and having it tag terms + companies wouldn’t work, because of the limits on context window size.

We’d have to upload a few at a time and then review the tags, which would be probably slower than tagging them manually.

Using AI to write scripts vs do stuff directly

But what AI shines at, more than anything, is writing utility scripts (in Javascript or Python) to perform a single task. A script is just a short bit of code, usually no longer than a few hundred lines, that does something very specific.

So — instead of asking a language model to tag posts directly, we had it write a script, using plain old Python, to parse all of the posts and suggest Terms and Companies that are mentioned in each post. Essentially, we had the model figure out how to automate the task instead of doing the task itself (which is actually also what we were doing).

The script cost about $.05 to write, and now runs for free in ~1 second, rather than having to continue to run up against a language model’s usage limits.

And, anytime we write a new post or add new universe terms, we can run that same script as part of our devops process, to make sure that we don’t betray your trust by missing a tooltip.

🖇 Workplace Example

This is one reason why learning to use the CLI (the terminal on your computer) is such an unlock: because you can run scripts that AI writes for you on your local files (docs, spreadsheets, images, PDFs etc).

It pairs nicely with version control, because even if you don’t fully understand the code within a script, you can always roll back changes that weren’t what you were looking for.

Knowledge base subscriptions

The main reason we built Technically 2.0 was to better organize information.

On Substack, there’s just one big feed of posts, so it’s hard to know where to start. Knowledge bases solve that problem by giving you clear direction on what to read and when, and are an extension of the original “Learning Tracks" on Technically.

A knowledge base (KB) consists of 4 technical pieces:

  • A list of posts that belong to the KB
  • The ability to subscribe / one-time purchase one securely with Stripe
  • Once a user subscribes, unlocking the KB and the posts within it with webhooks
  • Sending email updates when new posts are added to a knowledge base

The main complexity was implementing the ability to either a) offer a KB for free, b) sell it for a one-time payment, or c) sell it as a subscription.

Currently the only subscription KB is "Analyzing Software Companies", since it’ll be refreshed most frequently with company and category changes. Financiers please take note.

Giving Technically money

When reading a post, you'll encounter a friendly content bouncer at the paywall — like a librarian with a clipboard:

knowledge base checkout

This call to action will show up if you’re a) not logged in or b) not subscribed to a KB that the post belongs to.

Clicking “Sign up" or “Purchase" takes you on a journey to Stripe checkout, and ultimately back to the same post, but with all of the content unlocked.

How Stripe works

Stripe sells payments infrastructure for internet businesses: primarily, they help you bill your customers, process payments, and work with your payment data.

Instead of maintaining our own database of paying users and their card information, Stripe takes care of storing all of that securely, and gives us nice APIs to read and update that information.
From "What does Stripe do"

When you click "Subscribe," you're essentially ordering takeout: you place your order (start a Stripe checkout session), pay at the counter (Stripe's checkout page), and we prepare your meal (grant access) once the payment clears.

If it’s a free knowledge base (like "AI, it’s not that complicated"), you just pick up your food without paying.

Stripe handles all the credit card processing, so we don’t have to worry about storing your credit card info securely.

Webhooks hold it together

Webhooks are APIs, but backwards - they're ways for applications to send data to other applications, automatically.

Webhooks let applications send data around when important events happen, like a user signing up.
From "What are Webhooks?"

When something happens with your subscription (payment processed, card expired, subscription canceled), Stripe immediately pings Technically.

If you’ve made a purchase, Technically responds by running a database function to grant you access (literally adding a row for you to a database table), and then when you open posts within that knowledge base again they’re unlocked.

This means neither Justin nor I have to sit around manually granting access to you after you buy a knowledge base, which our significant others and pets appreciate.

Keeping you in the loop

We all have that friend who tells us when a band we like releases a new album. Now we at Technically can be that friend to you.

Technically uses Buttondown to send emails when we add or update posts within knowledge bases, which requires an integration with Buttondown via their API.

Basically when you sign up or unlock a knowledge base, every hour or so (using something called a “CRON" job), the Technically backend syncs that metadata (remember, data about data) to your email subscriber record in Buttondown—so that when we send updates to KBs, we know who to send them to.

Bringing your friends

Chances are you’re not the only one at your company who wants to learn more about software. From time to time Technically readers would ask if they can buy a team subscription, and that was pretty tedious to set up. So when we started building Technically 2.0 we set out to make that available out of the box.

Supporting teams is one of those not-app parts of building an app — it’s not sexy, you don’t really see it, but it needs to be there. It required a few pieces coming together:

  • A UI for users to send invites to their teams
  • A way for new users to accept those invites (probably the most complicated)
  • An admin UI to assign access to knowledge base seats once a new team member joins
  • A way to remove users from your team (since all things must end)

Put it all together, and it looks like this:

table of users and their knowledge base access This table took enough CSS that it spawned a :css-god: emoji in Technically Slack:

a special emoji

Sending an invite

When you send an invite, the form submits and calls an API endpoint , which uses the Resend API to send your teammate an email invitation:

inviting a coworker to technically

When inviting someone, you’re able to select knowledge bases to give them access to, so that they have something to do once they sign up (besides peruse all of the free posts).

It’s important to know how many seats you’ve purchased (but not yet assigned), so that you don’t sully your corporate reputation by sending a bad invite.

On the invite form, you’ll notice that each knowledge base is populated with a count of seats available, and it blocks you from attempting to invite a teammate to a knowledge base you don’t have seats to.

This is what’s known as form validation: the art of not letting users do things that wouldn’t work.

Accepting an invite

This is where things get tricky. That email invite contains a unique invite link, something like `technically.dev/accept-invite?id=some-long-random-id`.

Because Technically uses “Login with Google" authentication as mentioned above, you don’t actually login on Technically—you login via Google, who then tells the app who you are. This is SSO (single sign-on), and it’s another one of those not-app-building parts of building apps.

Because we “outsource" our authentication to Google, there are a bunch of round trips between our servers and theirs when a user creates an account, so it’s easy for that `inviteId` to be lost after you click the link.

Here’s how one gets around that:

  1. When the “accept invite" link is clicked, that inviteId is stored to the browser’s temporary local storage
  2. Another click to login with Google
  3. Google redirects back to the “callback URL" (where you’re sent after authentication completes)
  4. That URL (/auth/callback/) fetches your inviteId from local storage and processes your invite in the database: adds you as a member of an organization, adds access to any knowledge bases
  5. Your invite inviteId is deleted from local storage.

This is the kind of flow that likely happens billions of times a day across the internet, and we never notice it. Amazing stuff.

🔍 A deeper look

Did you know that your browser has a little database in it? It lets apps like ours store small little things when we need to, like the above flow.

You can check it out yourself: right click, click “inspect", and then find “application" in the topmost menu of the inspector. You should see something called “local storage" and that is where the magic is.

If you sent an invite to one of your burner emails and went to accept it with the inspector open, you’d see the inviteId populated (and then removed) as you go through the flow.

Other stuff we learned building Technically 2.0

Obviously there’s a lot more to this app than what’s in the post (otherwise, what took us so long?) but these are some of the most interesting features and how we built them.

If you’re curious for more detail, there’s a few topics we could cover in future posts:

  • How we used AI to help with development (we’re fans of the Aider command-line pair programmer). It’s great at some things, not so great at others, and we picked up a few practices that saved a fair bit of time.

  • The difference between server -side and client-side rendering in web apps. We spent a good chunk of time sorting out how to make Technically load super quickly—hope you’re feeling that!

  • As non-engineers, we’re paranoid about error monitoring, so we have a pretty sweet observability setup that lets us trace issues in a structured way across the frontend + backend.

Until then, enjoy the new Technically!