Scaling telehealth to millions: Hasura tips and tricks from Henry Meds
We’re bringing you blogs that summarize some of the very best talks from HasuraCon 2023.
This talk, “How Henry Meds Scaled Rapidly with Hasura,” was presented by Nathaniel Armer, CTO and Founder of Henry, a subscription healthcare service.
Henry uses Hasura to improve developer productivity, build scalable architectures, and handle security in a fast-growing telehealth startup. The following diagram shows how Hasura fits into Henry’s architecture.
Hasura amplifies developer productivity. Henry solutions look simple but have a lot of moving pieces: customers, providers, pharmacies, and internal teams all have different needs. One might think that Henry needs a very large engineering team, but they actually started using Hasura with one and a half engineers, and today they have a single small team.
Watch Henry’s full HasuraCon 2023 presentation here, or keep reading to get the full breakdown:
Your data structure is your API
With Hasura, Henry shifted from building APIs to modeling their data.
As backend engineers, they spent a lot of time figuring out how to connect routes to controllers, controllers to services, and services to the data access level. All of this work is replaced by Hasura – there’s so much busy work they don't have to do anymore. Using Hasura does require a change in thinking – your data structure is now your API.
Think about how your tables are structured and about your naming system. Decide up front on a consistent naming system because your names are not just internal, but the names for your public API.
With PostgreSQL, Henry uses schemas, which are like virtual databases, to logically group tables. For example, a customer schema might have a customer table, a profile table, and a settings table. That schema makes it much easier to see how these tables are connected.
Use built-in migrations
Hasura has built-in migrations, not just for Hasura schemas, but for Henry’s SQL. This is a huge time saver.
Nathaniel Armer, said, “I've worked at startups where we developed our own SQL migration strategies or used off-the-shelf solutions. Having SQL migrations built into Hasura gives us one less tool to manage.”
With Hasura, once you've figured out your data structure, you get your API for free. “Whether it's filtering or joining or sorting, or even really advanced features like live queries, Hasura provides it out of the box,” said Armer.
Double backend engineer productivity
The startups Armer has worked with had a one-to-one ratio between frontend engineers and backend engineers. With Hasura, Henry’s backend engineers are so productive that the ratio is two to one, doubling their productivity.
“Hasura freed up one of our engineers to work on other backend focus projects,” said Armer. “As a result, we're shipping features to customers faster than ever before.”
Benefits for frontend engineers
Henry’s frontend team appreciates Hasura’s GraphQL API. They no longer wait for backend engineers to painstakingly model the API. They have the control they want, a fully fledged API, and automatic type generation.
They still work closely with the backend team, but discussions are around data models and architecture, not details like sorting on a particular column.
Focus on the architecture
One engineer at Henry describes Hasura’s benefits this way: "The change in focus allows you as a developer to focus on the architecture and less on boilerplate."
That architectural shift has led the Henry team to explore other ways to handle business logic rather than writing controllers. They use triggers, remote schemas, and turning actions into data.
Triggers for business logic
Using stored procedures or triggers leverages functionality that's built into PostgreSQL, such as setting a date when a row is created or adding validating logic to a complex mutation before letting it commit. Henry uses pg_cron to schedule some of this logic in the database.
Remote schemas to expose external endpoints
Remote schemas enable Henry to expose external REST or GraphQL endpoints as part of their Hasura deployment. They use remote schemas for file uploads and downloads. Armer built a microservice that returns signed Google Cloud storage links. It is federated with Hasura and accessed like any other Hasura resource.
With remote schemas, you can also join external resources. For example, you could join a profile table to a Google Cloud storage resource with profile images and return signed links that let users view them. This technique keeps everything in the same API, but the files are uploaded and downloaded external to Hasura for greater efficiency.
Turning actions into data
Armer explains how Henry turns actions (such as emailing) into data. “As a backend engineer, if I'm asked to send an email, I grab my favorite email library,” says Armer. “But what happens if the email doesn't go out, or the code quits halfway through? As the project gets bigger and is split into multiple services, where should email sending be – in the onboarding flow or in the main application?”
To decouple data from coding workflows, Armer and his team created an email table with typical email fields and a sent-at field. A worker service polls the table for unsent emails and sends them automatically. If that service goes down, it sends emails the next time it polls. The table includes a sent-at timestamp, which makes it easier to diagnose and debug any problems.
When a new customer is added, Armer turns that action into data too. A trigger writes a welcome email to the email table, and the same worker service ensures the email is sent. “Turning actions into data is simple, transactional, and really elegant when you get used to it,” says Armer.
Hasura + Cloud Run: Serverless on the cheap
With Hasura doing so much heavy lifting, Henry avoids the monolith that startups typically build. But they still need a platform for logic that runs separately. They chose Cloud Run, which complements Hasura and combines the low cost of serverless with the ability to run Docker containers. Cloud Run supports complex multi-threaded code and scales to 100 instances.
One of the downsides of serverless is the need for long-running instances to support services like email service workers. “Normally, I would model this as a 24/7 service on a compute instance,” said Armer. “But what happens if that compute instance goes down?”
Instead, Armer made the service a Cloud Run instance and uses Cloud Scheduler to send regular cron-style events. “Every minute or so we send an event to check for emails and send them,” said Armer. “This lets us simulate a 24-hour service with the scaling and fault tolerance of serverless.”
In Cloud Run, versions are free; when unused, they scale to zero and cost nothing. With this in mind, the Henry team is experimenting with per-feature branch deployments. Every feature branch has its own URL and its own deployment live in the dev environment, which simplifies testing and sharing builds.
Even though Henry’s architecture looks sophisticated, it is very cost-effective. They don’t need dedicated DevOps staff, and their serverless cost per customer is less than pennies per month.
Authentication and authorization
Healthcare involves sensitive data and is heavily regulated. At Henry, they think about security and auditing from the start of any project.
Henry uses Google Cloud Identity with a custom microservice in front. The microservice generates JWTs and sends custom emails rather than using Identity's email feature.
When you generate JWTs, don't forget to set useful session variables like user email or user ID. You'll be able to use those session variables in Hasura. Like any JWT implementation, your client simply passes the token to Hasura to authenticate every request.
Armer appreciates Hasura’s authorization features. “I've really enjoyed using authorization with Hasura. It's built-in, it's powerful, and it's free,” he said.
Henry has implemented basic user roles in Hasura including a provider role, a customer service role, and an end-user role. They added a few feature-level roles to control specific granular functionality and use Hasura’s role-level control to show a user only their own profile.
Each role you set up requires some configuration, so strike a balance between control and speed.
Authentication and authorization are only half the story; Henry also needs to log every action.
They use database triggers to write audit logs on every mutation. Hasura includes useful context like session variables. However, this approach doesn't work for read queries and data sources that don't support triggers.
The second approach is to use the logs that Hasura generates. These logs cover both read and write actions, but aren't accessible from the database. For that, Henry uses Datadog to fetch the logs and send them to Google Cloud storage for long-term persistence.
In Henry’s architecture, many services go directly to Hasura rather than the database. Having Hasura centralize all data access gives Henry a central hub for auditing.