← 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.
Syntax
typescript
// shared/types.ts
export interface TypeName {
    property: type;
}

// imported by both client and server
import { TypeName } from "../shared/types";

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");
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Duplicating an interface like Order in both client and server, so the two copies drift apart.
  2. Assuming that shared types validate data at runtime, when a wrong JSON response still reaches the client.
  3. Importing server-only code (like database modules) into the shared package, which then breaks the browser build.

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.