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

I'm building a full-stack web application using modern JavaScript/TypeScript technologies. LLM has made it really easy for me to prototype frameworks and libraries with relatively low costs. This is Part 1 covering the tech stack and architecture decisions. 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 structure with proper foreign keys, JSON columns for flexible data, indexes on frequently queried columns, and audit fields on all tables.

Authentication with Multiple Methods

The authentication layer uses better-auth with email/password, WebAuthn/passkeys, OAuth social login, secure session management with HTTP-only cookies, and MCP integration for AI assistants:

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>;
}

This pattern eliminates the need for separate API routes and provides better performance through server-side rendering, all while maintaining complete type safety. The type safety extends from loader arguments to meta functions to client-side data consumption.

Why This Stack Works

Modern tooling matters. Vite provides fast HMR and automatic code splitting. Drizzle ORM offers prepared statements and connection pooling. Better-auth handles complex authentication patterns without boilerplate. React Router v7's typegen catches potential issues at compile time rather than runtime.

The combination creates maintainable, performant applications with excellent developer experience. Type safety is crucial - TypeScript caught numerous potential runtime errors during development.

This architecture demonstrates how modern JavaScript/TypeScript tooling can create production-ready applications. Continue to Part 2 for deployment, workflows, and performance considerations.