The Perfect Startup Tech Stack?

Adam FortunaAvatar for Adam Fortuna

By Adam Fortuna

10 min read

Every developer has a tech stack they consider to be the best. Mine has changed over time – and is changing again for Hardcover.

There is no “perfect tech stack”. I’m sorry you had to find out this way. Every startup is different. Some ideas can be turned into multimillion-dollar companies using only a spreadsheet and phone calls. Others require machine learning, big data, and years of research.

Hardcover is somewhere in the middle. We’re building a platform focused on social discovery and organization for serious book lovers. For that, we need a huge database of books – hundreds of thousands to millions of them.

If someone wants to track what books they want to read, they need a reliable place to find all books. This database has long been one of Goodreads primary advantages and one of our uphill battles.

We’re also a distributed team mostly working on this part-time. We span 4 time zones across 3 countries. The tools we use to work will be different than a group of friends collaborating in person.

With all of that out of the way, here’s a look at our perfect tech stack. It’s focused around a few important points:

  • Price – We’re bootstrapped, so free is better.
  • Distributed – Everything should allow us to work remotely.
  • Scalable – Finding the right balance of being scalable, but still able to go fast.

These are the tools and systems we’re using to build Hardcover today – after less than 2 months since we started.

Management & Organization

Miro$0. Next tier up: $96/mo. Miro is an amazing group whiteboard application that makes collaboration super-easy (barely an inconvenience). It allows us to do brainstorming exercises, dot voting, presentations, and more remotely all from the same application.

We’ve chosen to have one big whiteboard that we add to each week. As new members are added, they’re able to look back and get an idea of what got us to where we are today. We’ll likely create a new board at the beginning of every sprint.

The basic plan on Miro is free for unlimited users so long as you stay within the 5 boards limit. Since we’re using the same board for every meeting, that’s worked out so far.

Google Sheets & Docs$0. Next tier up: $5/user/month. – Google used to have a free “Apps for Your Domain” service that age free email addresses and GSuite access to everyone. A few years ago Google switched to charging $5/user/month.

Luckily there’s a way around this: just use your regular Google accounts! You can collaborate together using this without the added fee.

Notion$0. Next tier up: $8/team member – I love Notion. I’ll head over to YouTube and just watch videos on Notion for fun. I’ve even used it to track books I’ve read or want to read.

For project management and organizing what people are working on, Notion is works. It’s not as feature-rich as Asana, Monday or Clickup, but a startup in the earliest stages doesn’t need half of those features.

In fact, I’d recommend skipping it altogether until it’s a problem. We only started tracking what we’re working on this past week at the 5-week mark. Even that seemed too early.

GitHub$0 – $4/user/month for advanced features. Like just about every development project, we host our code on GitHub. For past companies I’ve worked at (Pluralsight, Code School), we’d organize issues within a single repository. Each repo is a central location for all tickets.

For startups, it’s even easier. At the organization level, you can create “projects” that span multiple repositories. In our case, we have 2 repos now (the front-end and the backend-end apps), and the project at the organization level allows all work to be represented in one place.

Current Management Cost: $0 for now

Services

The Sendy Dashboard makes it super-easy to send emails to your list.

Sendy$69 + pennies a month – Newsletter software that’s super cheap but scales well. You could use Mailchimp for your first 1k users for free, but it’s more expensive after that. I’ve been using it with another project for a while, so it was easy to use it here with the same account. I’m only hosting one instance of Sendy, but I can use it for multiple projects.

WordPressFree – The blog portion of Hardcover is WordPress. Behind the scenes, Next.js uses GraphQL to pull in the latest posts and show them. End users never know it’s WordPress. There’s a great Learn With Jason episode that helped me build this out.

Dreamhost$2.59/mo – I host both Sendy and WordPress here for Hardcover. It’s all on the same account as Minafi’s (another project of mine) Sendy and WordPress, which has made this free. The next site I work on will also be on here for these services.

Current Services Cost: $0 for now

The Code

Time to get into the fun stuff (well, for me at least). I’ve been building websites since 1998 when I created one my first site ever (an anime fan site).

Over the years my preferred tech stack has changed more times than I can count. From straight HTML & CSS to PHP & MySQL to ColdFusion to Flex & Java to Ruby on Rails (can you tell I switched from the enterprise to a startup?).

For the last decade Ruby on Rails was my life. It was part of Code School’s DNA since launching with Rails for Zombies – an interactive course to learn Rails, built on Rails. The Course Team (which I managed and coded on) was also a Ruby on Rails app. Most of our services were Rails apps. It was Ruby all the way down.

Since then I’ve typed rails new more than a hundred times. Each time I got a little faster at building with it.

When it came time to decide what technology to use for Hardcover, I assumed I’d just use Rails again. It keeps everything organized, it’s fast (enough) and there are libraries for just about everything you could ever ask for.

Over the last year I worked on two smaller-scale Ruby on Rails applications – each with a JavaScript front end. One used Vue.js and REST, the other Vue.js and GraphQL through Rails. While these apps were fast, building out every GraphQL endpoint was a pain. I was also missing out on one of the most important parts of speedy apps: server side rendering.

For these apps that I built with Rails + a JS framework, the first page load would involve a blank page that would then be hydrated with data. It wasn’t exactly what I wanted for every page, but it was all I had. I couldn’t precompile Vue.js components on the server side.

When it came time to choose a tech stack for Hardcover I knew I wanted server side rendering from the start. I had a few other things on my list too:

  • Server Side Rendering
  • React – specifically for React Native down the line
  • Easy integration with TensorFlow (Python or JavaScript)

It was looking clear to me that this would be a JavaScript project – and not just because I know barely any Python.

JavaScript would also have the added advantage of being able to stick to just one language on the front-end and back-end, while even sharing code libraries between the two.

It would mean breaking away from my comfy blanket that is Ruby on Rails, but the payoff could be big. So with that in mind I began researching out what the tech stack should include. Here’s a look at what I landed on so far.

Next.js & React – I’m loving both of these for building it. React should allow for using React Native later, which means native iOS and Android apps down the line.

I’ve used a lot of frameworks over the years, but Next.js is without a doubt the easiest one I’ve ever seen. There are no routes and no configuration you need to setup. Just create a file at a specific location, have it return a React component and bam, you have a webpage. I’ve honestly spent more time getting Webpack working with Rails than building major pieces of Hardcover.

Vercel$20/mo for team hosting, free for individuals – We’re hosting the Next.js app on Vercel – a cloud hosting platform built by the creators of Next.js. Their obsession with speed has made me smile.

PostgreSQL$200/mo – My goto database for anything now a days. About a decade ago I was working on a MySQL project. I added a new column to a table with 10 million rows. It ended up locking the table for over 30 minutes, assuring no one could access it. The worst part? It was the users table. Now I just use PostgreSQL for everything.

Wait, $200 a month? It turns out creating a database to hold 20,000,000 isn’t cheap. So far this is one cost we haven’t been able to defer or skip. On Heroku, $200 gets you a database with 8GB of RAM, 256 GB of storage, automatic backups, followers, forks and 400 connections. Eventually we’ll want to look into other hosting. I’m not a DevOps expert, so having this managed saves me a lot of stress and helps me sleep better at night.

Hasura & GraphQL – $0 to start, $99/mo for Hasura Cloud – I only learned about Hasura in April and I’m completely in love with it. It’s software – either cloud hosted by them, or hosted on your own – that creates a GraphQL API to your PostgreSQL database. You can setup permissions per user that go all the way down to the row and column level.

What’s been amazing about Hasura is that we’re able to focus on filling our database with books and building the team, not focused on creating a GraphQL API for everything we might want to use. Instead, we can just use what Hasura offers, or fallback on our Express.js server (next) if it’s beyond it’s capabilities.

Express.js – Our backend for code we don’t need available to the client. This API is never hit by the end user, but does handle a bunch of heavy lifting (importing people’s lists, keeping books up to date, etc). We’ve setup a number of triggers in Hasura that will hit Express.js endpoints.

The Express.js app also has a GraphQL endpoint that’s federated with Hasura. Let’s say the front-end wants to get a user and 10 recommended books for that user. We can issue a single GraphQL call to Hasura, but request data from the Express.js side. This allows us to use TensorFlow.js on the backend to generate and return these recommendations to the user with a beautifully-simple front-end coding experience.

Bull – Not everything can be handled via a web request. For background processing and anything that happens outside the browser we have a Bull queue backed by Redis. This handles any long-running tasks. Since Node.js is amazing at this kind of work, we’re able to run 50 jobs per worker, with 2 workers per process. That’s 100 jobs at a time.

Think of it like this: With Node.js there’s only 1 thing that’s actually happening at a time per process. But when that thread is waiting – like when it’s waiting for a database query to complete – it can start running code from another job. Node.js repeats this process until it has 50 things waiting, or until some of them complete. Whichever one completes first continues on. When it pauses again or completes, work resumes on another job.

This allows things like our Book Importer to process 100 books at a time per worker – and we can scale up our workers to handle up to about 10,000 a time if needed before we hit our database connection limit.

TensorFlow.jsopen source – I am no expert in TensorFlow, but I’m in awe of what it can do. I’m reading through Deep Learning with JavaScript: Neural networks in TensorFlow.js right now, and it’s been mind-warping. So far we’ve done some proof of concepts to test out ideas, but we’re still a ways away from using this yet. Once we get some users we’ll start creating TensorFlow models for the most important bits (Match % and Recommendations) come to mind.

Heroku$260 – The Express.js app, Bull worker queue, Redis and Postgres are all hosted on Heroku. None of those are directly accessed by the end user, so this piece is all behind the scenes.

Heroku isn’t cheap. The total hosting cost to get started with this is around $260/month. $200 for the database, $25 for Express, $25 for Bull and $10 for Redis.

When/if someone joins the team with DevOps experience I’m sure we’ll be able to lower the cost on this. Luckily this is our only bill for the moment, so it’s not an emergency.

Server Architecture

That was kind of a lot. Recently I was onboarding a new team member and drew up this quick diagram of how it all works together.

Yes, I somehow spelled “Server” wrong. There should be a Redis instance that Express.js and Bull can access too.

Here’s the thing with this diagram: it could have all been built as a single Ruby on Rails app with a PostgreSQL database. We could have gone that route to build out a quick prototype and go from there.

Let me be real for a moment here: I think we’ll be able to move faster with this setup. Even though I’m needing to learn Next.js and Hasura as we go, being able to have most of the API created for free is a game changer. Instead of writing API endpoints non-stop, I’m able to focus on user research.

The advantages don’t stop there. It already looks like we’ll be able to scale up with an easier path as well:

  • Quick access to a full GraphQL API (Hasura)
  • Easily iterate into a React Native App (React, Hasura)
  • Easily scale the API (Postgres Followers + Hasura distributed over them)
  • Machine Learning Integration (TensorFlow.js, Express + Federated GraphQL).
  • Easily migrate from Hasura Cloud to a self-hosted version.
  • Migrate from Heroku to Google Cloud or Azure (off AWS because we’re building a product out of spite for Amazon).

When I tried to look into the future to see what we’d need to launch publicly – not just a proof of concept – it went beyond the basics. It’s tough to have a book website that can’t import all of your existing books. For that you need a seriously big database or a long import process that abuses Google Books API.

While it’s still in the wheelhouse of what Ruby on Rails offers, I’m excited to try something new. More than anything else, I think this stack offers some of the fastest load times for users. A typical page load might look something like this:

  • User downloads a pre-rendered HTML page for a book.
  • Client side JS jumps in and fills in any user-specific parts of the page.
  • The only API hit in this process is Hasura, which in turns hits the PostgreSQL database directly.

That’s really about it. There are a lot of parts we’ll use down the line, but 99% of page requests will be this simple. While it’s sometimes fun to dream about how a project could scale, getting the user interaction side as fast as possible while optimizing for developer productivity is the technical goal in all of this. Keep that in mind when planning your perfect tech stack.

← More from the blog

Want to Read More?

Sign up to be notified of new articles, and be the first to join Hardcover when we launch.

We'll email you so you can snag your username on Hardcover before anyone else.