
Table of Contents
Jump to a section
Building apps with AI is easy now, right? One thing that helps me a lot is having one common tech stack across all my projects. I try to not change it up at all anymore, just to stay consistent.
Some features I love about my tech stack are simple.
- Simplicity - It shouldn't require a lot to understand that
- Maintainable - It should be easy to maintain this tech stack in the future
- Longevity - I want the tech stack to live, evolve, and survive out there!
There are some main components of a fullstack application where you need a service, framework, or something else for. These are:
- Backend: Where does your API live
- Database: What kind of database do you use?
- Frontend: The website your users are interacting with. This includes styling for me
- Deployment: How to publish your whole application?
- Async: How do you run stuff in the background?
- Authentication: How can your users authenticate?

Let's go through each and every category to see why I'm using what I use.
Backend - Lambda with Hono API
I fell in love with Hono API. It is simple, it has a known chain syntax, is type-safe, and I can do a lot with it.
Look at that code and tell me you don't understand what it is doing:
app.post('/purchase', zValidator('json', analyticsIngestSchema), async (c) => {
l.info('Purchase tracking request received');
const { orderId, referrer, pagePath, utmParameters } = c.req.valid('json');
Hono API follows a chain syntax pretty similar to Express API. You can write custom middlewares, but Hono comes already with tons of middlewares.
As compute I typically use one monolithic Lambda function. Either behind a function URL. Or behind a REST API Gateway as one integration.
This makes it easily testable, debuggable, and the overall experience is nice & simple.
I love having an RPC App Client of my Hono API exported to my frontend. With that even my frontend has a type-safe API client which is completely underrated.
export type AppType = typeof routes;
const client = hc<AppType>(API_URL);
client.admin.stats.$get().then((res) => {
if (res.ok) {
res.json().then((data) => {
All of the above is completely typesafe now!
Alternatives I used in the past and don't want to use anymore:
- AppSync: GraphQL can be nice. But honestly it overcomplicates things, caching can be hard, and not everybody knows it. Just go with REST.
- TRPC: Same thing. Just too complicated to set up.
- Direct Integrations (VTL): Horrible to debug. Nobody understands it. Nobody knows it. But it can have its place.

AWS Lambda on One Page (No Fluff)
Skip the 300-page docs. Our Lambda cheat sheet covers everything from cold starts to concurrency limits - the stuff we actually use daily.
HD quality, print-friendly. Stick it next to your desk.
Database - Postgres
I changed my approach on databases a lot in the past. I was a huge DynamoDB & Single Table Design fan. But honestly, NoSQL is just not meant for 95% of all applications.
Explain to your stakeholders why it is that hard to count how many users there are with the name Alex. This can't be that hard, right?
I love using SQL. It forces you to think about your data. How it relates to each other, which type it can be, and what happens if you delete something.
I always go with Postgres now. If I don't go all in with AWS I use Supabase or Planetscale. But on AWS I simply go with RDS and that's it.
As an ORM I always use Drizzle. I love Drizzle. Drizzle is also fully type-safe, has an amazing community, and has so many capabilities which is amazing. I use Drizzle completely for migrations, looking at my data (studio), and for interacting, reading & writing to my database.
Frontend - TanStack Start SPA only
Also with my frontend technologies I went full circle.
PHP / HTML Fully server rendered -> React client-side only -> React with server-side rendering -> Fully static with Gatsby -> Mix & Match -> Client-side only
Now, each project for me is:
- Vite
- React
- TanStack Start SPA only
I don't need or want server-side rendering. I want a webpage with some loading indicators (now called suspense ๐ง๐ฝ) and that's it. It should login, authenticate with an API, and get data from this API. The rest is simply a CRUD app, right?
Deployment - CDK, CloudFront, S3, Lambda
For deployment I almost always use the following:
- CDK for defining my infrastructure. Battle-tested, not perfect, but good enough
- CloudFront & S3 for hosting my frontend application (SPA only so no extra backend needed)
- Lambda for hosting my API
- GitHub Actions for deploying my application on
git push
Nothing fancy. If I require shorter cold-starts (e.g. for my Shopify apps) I host my API in a container on ECS Fargate. This could be even more simplified and be cheaper but currently I'm quite happy with this setup.
Async - EventBridge, SQS, and Lambda
Once your application evolves you will need to have some asynchronous workloads, believe me. Some examples where I need them:
- Incoming webhooks (orders from eCommerce)
- Cron Jobs (run the world ๐๏ธ)
- Payments that failed
I love to integrate them with AWS Partners in EventBridge. For example, Stripe & Shopify have a native integration of their events into EventBridge. These events are free. I can listen to them and attach a Lambda function or SQS queue to these events.
Typically, for "low-scale" events I just attach a Lambda function which listens to these events. For "higher-scale" events such as orders I add an SQS queue which works on events in batches.
This is a perfect scalable setup and is surprisingly minimal, once you've figured it out.
Authentication
Here I have again two replies:
- Fully AWS: Cognito
- Free to choose: Better Auth
I love Better Auth. It is kind of a self-hosted authentication. But it is so freaking simple.
Cognito comes with User Pools, Identity Pools, Settings for each, Duplicated Users, App Clients, Hosted Pages, etc.
Better Auth just gives you integrations for nearly every login possible. And also plugins to make things super straightforward like
- multi-tenancy
- roles & scopes
- admins
Better Auth is opinionated. But I think in a very good way. So, I can only recommend it!
Summary
That is it!
This is the tech stack I use for every new project by now.
I "bundle" everything together in one pnpm workspace.
I have an example project lying on my disk. Once I start a new project I simply tell Claude to copy this exact project structure. And I can get started working on it right away.


