Table of Contents
- Understanding the Modern TypeScript Ecosystem
- Scaffolding a Basic TypeScript Project with
tsc - Modern Scaffolding with Vite
- Scaffolding a Node.js TypeScript Project
- Integrating Essential Tooling (Linting, Formatting, Testing)
- Tsconfig.json Deep Dive
- Advanced: Scaffolding Monorepos with Turborepo
- Conclusion
- References
1. Understanding the Modern TypeScript Ecosystem
Before diving into scaffolding, let’s map the key tools that power modern TypeScript projects:
TypeScript Core
tsc: The official TypeScript compiler, converts TypeScript (.ts) to JavaScript (.js).tsconfig.json: Configures the compiler (e.g., target JS version, module system, strictness).
Package Managers
- npm: Default Node.js package manager.
- Yarn: Faster alternative with deterministic installs.
- pnpm: Space-efficient with a shared dependency cache (great for monorepos).
Build Tools & Bundlers
- Vite: Next-gen frontend build tool (uses esbuild for fast HMR and Rollup for production bundling).
- esbuild: Ultra-fast JavaScript/TypeScript bundler (used under the hood by Vite).
- ts-node: Executes TypeScript files directly without pre-compiling (ideal for Node.js).
Linting & Formatting
- ESLint: Identifies code errors and enforces style rules (with
@typescript-eslintfor TS support). - Prettier: Automatically formats code for consistency (works seamlessly with ESLint).
Testing
- Vitest: Vite-native test runner (fast, ESM-first, TypeScript-friendly).
- Jest: Popular testing framework (works with TypeScript via
ts-jest).
2. Scaffolding a Basic TypeScript Project with tsc
Let’s start with the fundamentals: a minimal project using the official TypeScript compiler (tsc). This is ideal for learning or small scripts.
Step 1: Initialize the Project
First, create a project folder and initialize a package.json:
mkdir ts-basic-demo && cd ts-basic-demo
npm init -y # -y skips prompts and uses defaults
Step 2: Install TypeScript
Install TypeScript as a dev dependency (we’ll use --save-dev since it’s a build tool):
npm install --save-dev typescript
Step 3: Configure tsconfig.json
Generate a tsconfig.json (TypeScript’s configuration file) with:
npx tsc --init
This creates a default tsconfig.json with hundreds of commented options. Let’s simplify it for our needs:
{
"compilerOptions": {
"target": "ES2020", // Compile to ES2020 (modern browsers/Node.js)
"module": "ESNext", // Use ES modules (import/export)
"outDir": "./dist", // Output compiled JS to `dist/`
"rootDir": "./src", // Source TS files live in `src/`
"strict": true, // Enable all strict type-checking options
"esModuleInterop": true, // Allow `import` of CommonJS modules
"skipLibCheck": true, // Skip type-checking of .d.ts files (faster)
"forceConsistentCasingInFileNames": true // Avoid OS-specific casing issues
},
"include": ["src/**/*"], // Include all TS files in `src/`
"exclude": ["node_modules"] // Exclude dependencies
}
Step 4: Write Your First TypeScript File
Create a src folder and add a main.ts file:
// src/main.ts
function greet(name: string): string {
return `Hello, ${name}!`;
}
const user = "TypeScript Developer";
console.log(greet(user)); // Output: Hello, TypeScript Developer!
Step 5: Build and Run the Project
Add a build script to package.json:
{
"scripts": {
"build": "tsc", // Compile TS to JS
"start": "node dist/main.js" // Run the compiled JS
}
}
Now build and run:
npm run build # Generates dist/main.js
npm start # Runs the script
Output:
Hello, TypeScript Developer!
Limitations of this setup: No live reloading, slow builds for large projects, and no built-in bundling for browsers. For modern apps, we’ll use tools like Vite next.
3. Modern Scaffolding with Vite
Vite has revolutionized frontend development with its speed and simplicity. It uses esbuild for pre-bundling dependencies and Rollup for production builds, making it perfect for TypeScript projects.
Why Vite?
- Fast HMR: Updates reflect instantly without full page reloads.
- Optimized Builds: Rollup produces smaller, tree-shaken bundles.
- Zero Config: TypeScript support out of the box (no manual
tsconfigsetup needed).
Scaffolding with Vite
Run the Vite starter command and follow the prompts:
npm create vite@latest my-vite-ts-app
- Select a framework (e.g.,
Vanilla,React,Vue—we’ll useVanillafor simplicity). - Choose
TypeScriptas the variant.
Project Setup
Navigate to the project and install dependencies:
cd my-vite-ts-app
npm install
Project Structure
Vite generates a clean structure:
my-vite-ts-app/
├── node_modules/
├── public/ # Static assets (e.g., images)
├── src/
│ ├── main.ts # Entry point
│ ├── vite-env.d.ts # Type definitions for Vite
│ └── style.css
├── index.html # HTML entry (Vite uses this to inject scripts)
├── package.json
├── tsconfig.json # Pre-configured for Vite
└── vite.config.ts # Vite-specific config
Run the Development Server
Start the dev server with:
npm run dev
Vite will launch a server at http://localhost:5173. Edit src/main.ts, and changes will appear instantly!
Build for Production
To bundle for production:
npm run build
Vite outputs optimized files to dist/, ready for deployment. You can preview the build with:
npm run preview
4. Scaffolding a Node.js TypeScript Project
For backend projects (APIs, CLIs), use ts-node to run TypeScript directly and nodemon for live reloading.
Step 1: Initialize and Install Dependencies
mkdir ts-node-demo && cd ts-node-demo
npm init -y
npm install --save-dev typescript ts-node nodemon @types/node
ts-node: Executes TypeScript without pre-compiling.nodemon: Restarts the server on file changes.@types/node: Type definitions for Node.js APIs.
Step 2: Configure tsconfig.json
Generate and update tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS", // Node.js uses CommonJS by default
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}
Step 3: Add a Server Script
Create src/server.ts:
// src/server.ts
import http from "http";
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello, Node.js TypeScript!");
});
const PORT = 3000;
server.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
Step 4: Add Nodemon Scripts
Update package.json scripts:
{
"scripts": {
"dev": "nodemon --exec ts-node src/server.ts", // Live reload
"build": "tsc", // Compile for production
"start": "node dist/server.js" // Run compiled JS
}
}
Start the dev server:
npm run dev
Visit http://localhost:3000—you’ll see Hello, Node.js TypeScript!. Edit server.ts, and nodemon will auto-restart the server.
5. Integrating Essential Tooling
Modern projects require linting, formatting, and testing. Let’s add these to our Vite or Node.js project.
Linting with ESLint + TypeScript
ESLint catches errors and enforces code style.
Install Dependencies
npm install --save-dev eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser
Configure ESLint
Create .eslintrc.js:
module.exports = {
root: true,
parser: "@typescript-eslint/parser", // Parse TypeScript
plugins: ["@typescript-eslint"], // TypeScript-specific rules
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended", // Recommended TS rules
"plugin:@typescript-eslint/strict-type-checked", // Strict type-checking
],
parserOptions: {
project: "./tsconfig.json", // Use our tsconfig for type info
},
};
Add a lint script to package.json:
"scripts": {
"lint": "eslint src/**/*.ts"
}
Run linting:
npm run lint
Formatting with Prettier
Prettier ensures consistent code formatting.
Install Prettier
npm install --save-dev prettier eslint-config-prettier
eslint-config-prettier: Disables ESLint rules that conflict with Prettier.
Configure Prettier
Create .prettierrc:
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5"
}
Update .eslintrc.js to extend Prettier:
extends: [
// ...existing extends
"prettier" // Add this last to disable conflicting rules
]
Add format scripts:
"scripts": {
"format": "prettier --write src/**/*.ts", // Auto-format files
"format:check": "prettier --check src/**/*.ts" // Check for unformatted files
}
Testing with Vitest
Vitest is a fast, Vite-native test runner. For Vite projects, it’s pre-configured—just install:
npm install --save-dev vitest
Add a test script to package.json:
"scripts": {
"test": "vitest"
}
Write a test in src/main.test.ts:
import { describe, it, expect } from "vitest";
import { greet } from "./main"; // Assume we exported `greet` from main.ts
describe("greet", () => {
it("returns a greeting message", () => {
expect(greet("Test")).toBe("Hello, Test!");
});
});
Run tests:
npm test
6. Tsconfig.json Deep Dive
tsconfig.json controls TypeScript’s behavior. Here are critical options:
Key Configuration Options
| Option | Purpose |
|---|---|
target | JS version to compile to (e.g., ES2020, ESNext). |
module | Module system (CommonJS for Node.js, ESNext for browsers). |
outDir | Where to output compiled JS (e.g., ./dist). |
rootDir | Root folder for source TS files (e.g., ./src). |
strict | Enables all strict type-checking (always set to true). |
esModuleInterop | Allows import of CommonJS modules (e.g., import fs from 'fs'). |
moduleResolution | How modules are resolved (Node for Node.js, Bundler for Vite/Rollup). |
Strict Mode: Why It Matters
Strict mode enables rules like:
noImplicitAny: Disallows untyped variables (avoidsanypitfalls).strictNullChecks: Requires explicit handling ofnull/undefined.strictFunctionTypes: Ensures function parameters are type-safe.
Never disable strict mode unless migrating a legacy JS project!
7. Advanced: Scaffolding Monorepos with Turborepo
For large projects (e.g., frontend + backend + shared libs), use a monorepo tool like Turborepo to manage multiple packages efficiently.
What is a Monorepo?
A monorepo houses multiple projects (apps/packages) in a single repo, enabling shared code, unified tooling, and coordinated versioning.
Scaffold with Turborepo
npx create-turbo@latest
Follow the prompts to create a monorepo with:
- A
packages/folder for shared code (e.g.,eslint-config,ui). - An
apps/folder for apps (e.g.,web(Vite frontend),api(Node.js backend)).
Run Commands Across Projects
Turborepo runs commands in parallel and caches results for speed:
# Build all apps/packages
npm run build
# Run tests for all projects
npm run test
# Start dev servers for web and api
npm run dev
8. Conclusion
Scaffolding TypeScript projects has never been easier with modern tools like Vite, ts-node, and Turborepo. To recap:
- For frontend apps: Use Vite for fast HMR and optimized builds.
- For Node.js backends: Use
ts-node+nodemonfor live reloading. - For large projects: Use Turborepo to manage monorepos efficiently.
- Always enable
strict: trueintsconfig.jsonfor type safety. - Integrate ESLint, Prettier, and Vitest/Jest for code quality and testing.