KahWee - Web Development, AI Tools & Tech Trends

Expert takes on AI tools like Claude and Sora, modern web development with React and Vite, and tech trends. By KahWee.

Building a Modern Full-Stack Web Application - Part 1

LLMs made it cheap to prototype frameworks and libraries. I used that to build a full-stack web application with modern TypeScript tooling. This is Part 1: tech stack and architecture. Part 2 covers deployment and workflow.

Tech Stack

The foundation is React Router v7 with TypeScript, PostgreSQL for data, and modern UI libraries:

Frontend: React Router v7 with React 19.1.1, TypeScript 5.9.2 for type safety

UI & Styling: Tailwind CSS v4.1.13, Catalyst UI components, Headless UI, Heroicons, Motion for animations

Backend & Data: Node.js v24+, PostgreSQL with Neon serverless, Drizzle ORM v0.44.5 for type-safe queries

Authentication: better-auth v1.3.9 with WebAuthn, OAuth, and session management. Zod v4.1.5 for validation

Dev Tools: Vite v7.1.5, ESLint, Prettier, Vitest, Playwright, Storybook

Architecture Decisions

Type-Safe Data Layer

Using Drizzle ORM provides full type safety across the entire data layer:

// Type-safe queries with full IntelliSense
const users = await db.query.users.findMany({
  where: eq(users.isActive, true),
  with: {
    profile: true,
    settings: true,
  },
});

// Prepared statements for performance
const getUserById = db
  .select()
  .from(users)
  .where(eq(users.id, sql.placeholder("id")))
  .prepare();

The database schema uses normalized tables with foreign keys, JSON columns for flexible data, and indexes on frequently queried columns.

Authentication with Multiple Methods

The authentication layer uses better-auth: email/password, WebAuthn/passkeys, OAuth, and HTTP-only cookie sessions.

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: "pg",
  }),
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
  },
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
  },
  passkey: {
    enabled: true,
  },
});

API Architecture with React Router v7 Typegen

React Router v7's typegen automatically generates route-specific types based on your file structure:

import type { Route } from './+types/app.dashboard._index';

export const meta: Route.MetaFunction = () => {
  return [
    { title: 'Dashboard - My App' },
    { name: 'description', content: 'Application dashboard overview' },
  ];
};

export async function loader({ params, request }: Route.LoaderArgs) {
  const user = await requireAuth(request);
  const data = await getData(params.id);
  return { data, user };
}

export default function Component() {
  const { data, user } = useLoaderData<typeof loader>();
  return <div>...</div>;
}

Tip

This pattern eliminates separate API routes. Type safety extends from loader arguments to meta functions to client-side data consumption.

Why This Stack Works

Vite gives fast HMR and automatic code splitting. Drizzle ORM handles prepared statements and connection pooling. Better-auth removes authentication boilerplate. React Router v7's typegen catches bugs at compile time instead of runtime.

TypeScript caught dozens of potential runtime errors during development. That alone justified the stack choice.

Continue to Part 2 for deployment, workflows, and performance.