← Back to TypeScript Course | Chapter 28: Real World Projects | Lesson 6 of 14

Full Stack TypeScript App

A full-stack TypeScript application can share models between a frontend and backend. This reduces duplicated contracts between the two sides.

Core Concept

A full-stack TypeScript app shares type definitions between its frontend and backend, so a request/response shape defined once is enforced identically on both the client that sends it and the server that receives it.

Example: Core Concept

typescript
interface Order {
  id: string;
  total: number;
}
// Shared between an Express route and a React component.
const order: Order = { id: "1", total: 99.99 };
console.log(order);

Basic Setup

A basic setup is usually a monorepo with packages/client, packages/server, and packages/shared (holding the common interfaces), wired together with workspace references so all three type-check as one project.

Example: Basic Setup

typescript
// packages/client, packages/server, packages/shared
// wired together with workspace references
console.log("A monorepo with a shared package ties client and server together");

Typed Example

A typed example: a shared interface Order { id: string; items: OrderItem[]; total: number } gets imported by an Express route that returns it and a React component that renders it, guaranteeing both sides agree on its shape.

Example: Typed Example

typescript
interface OrderItem { name: string; price: number; }
interface Order {
  id: string;
  items: OrderItem[];
  total: number;
}
const order: Order = { id: "1", items: [{ name: "Book", price: 20 }], total: 20 };
console.log(order);

Project Usage

In a real project, this shared-types pattern is what makes a backend field rename surface as an immediate compile error in the frontend, instead of a silent runtime bug discovered by a user days later.

Example: Project Usage

typescript
interface Order { id: string; total: number; }
// A backend field rename to Order breaks the frontend at compile time,
// not silently at runtime days later.
function renderOrder(order: Order) {
  return `Order ${order.id}: $${order.total}`;
}
console.log(renderOrder({ id: "1", total: 50 }));

Best Practices

Keep the shared package free of framework-specific code (no Express types leaking into it, no React imports) so it stays usable from both the Node backend and the browser frontend without pulling in irrelevant dependencies.

Example: Best Practices

typescript
// shared/order.ts -- no Express or React imports here
export interface Order {
  id: string;
  total: number;
}
console.log("Shared package stays framework-free, usable by both sides");

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.