Hands‑On Exercise: Add TypeScript to a Small Node Project¶
In this exercise, you’ll take a tiny Node.js project written in plain JavaScript and convert it to TypeScript. You’ll see how TypeScript improves safety, tooling, and maintainability—without changing how the code runs.
Step 1 — Start With a Plain Node Project¶
Create a new folder:
mkdir node-ts-demo
cd node-ts-demo
npm init -y
Create a simple JavaScript file:
index.js¶
const fs = require("fs")
const path = require("path")
function loadConfig() {
const filePath = path.join(__dirname, "config.json")
const raw = fs.readFileSync(filePath, "utf8")
return JSON.parse(raw)
}
const config = loadConfig()
console.log("Config loaded:", config)
Create a config file:
config.json¶
{
"version": "1.0.0",
"debug": true
}
Run it:
node index.js
Everything works—but there are no types, no autocomplete, and no safety.
Step 2 — Install TypeScript¶
Install TypeScript and Node types:
npm install --save-dev typescript @types/node
Initialize a TypeScript config:
npx tsc --init
This creates a tsconfig.json.
Step 3 — Configure TypeScript for Node¶
Open tsconfig.json and update the essentials:
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"rootDir": "./src",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true
},
"include": ["src"]
}
Create the source folder:
mkdir src
Move your JS file:
index.js → src/index.js
Step 4 — Convert the File to TypeScript¶
Rename:
src/index.js → src/index.ts
Now TypeScript will start analyzing it.
Step 5 — Add Types to Node Imports¶
Replace CommonJS with ES modules:
import fs from "fs"
import path from "path"
TypeScript automatically knows the types because of @types/node.
Step 6 — Type the loadConfig Function¶
Right now, TypeScript infers any for JSON.parse.
Let’s fix that.
Define a config type¶
type AppConfig = {
version: string
debug: boolean
}
Type the function¶
function loadConfig(): AppConfig {
const filePath = path.join(__dirname, "config.json")
const raw = fs.readFileSync(filePath, "utf8")
return JSON.parse(raw) as AppConfig
}
Now TypeScript knows:
config.versionis a stringconfig.debugis a boolean- accessing a missing field is an error
Step 7 — Add Error Handling (Optional but Recommended)¶
Let’s make the function safer:
function loadConfig(): AppConfig {
const filePath = path.join(__dirname, "config.json")
if (!fs.existsSync(filePath)) {
throw new Error("Config file not found")
}
const raw = fs.readFileSync(filePath, "utf8")
const parsed = JSON.parse(raw)
return parsed as AppConfig
}
TypeScript ensures:
existsSyncreturns a booleanreadFileSyncreturns a stringparsedmust matchAppConfig
Step 8 — Compile and Run¶
Compile:
npx tsc
Run the output:
node dist/index.js
You now have a fully typed Node project.
Step 9 — Add a Second File (Optional)¶
Create a helper:
src/logger.ts¶
export function log(message: string): void {
console.log(`[LOG] ${message}`)
}
Use it in index.ts:
import { log } from "./logger"
log(`Loaded version ${config.version}`)
TypeScript ensures:
logreceives a stringconfig.versionis typed- imports are validated
Step 10 — Observe TypeScript’s Benefits¶
Try breaking things:
config.version = 123 // ❌ Error
log(42) // ❌ Error
fs.readFileSync(10) // ❌ Error
TypeScript catches all of these before runtime.
What You Learned¶
This hands‑on exercise shows how TypeScript improves a Node project:
1. TypeScript integrates seamlessly with Node¶
fs,path,http, etc. have built‑in types@types/nodeprovides full coverage
2. TypeScript makes JSON parsing safer¶
- define a type
- cast the parsed result
- catch mismatches early
3. TypeScript improves project structure¶
src→ TypeScript sourcedist→ compiled JavaScript
4. TypeScript prevents common Node bugs¶
- wrong argument types
- missing files
- incorrect return values
- unsafe JSON parsing
This is exactly how real teams migrate JavaScript Node projects to TypeScript—incrementally, safely, and with immediate benefits.