How to Set Up a TypeScript Project from Scratch
Create a TypeScript project from scratch: tsconfig.json, strict mode, and build scripts. A hands-on setup guide for Node.js and TypeScript.
Why strict mode from day one
TypeScript’s value is catching mistakes before runtime — but only if strict is
on. Turning it on later means fixing a wall of errors at once. Enable it now,
when there’s no legacy code to migrate.
Step 1 — Initialize the project
mkdir my-ts-project && cd my-ts-project
npm init -y
npm install -D typescript tsx @types/node
typescript— the compiler.tsx— runs.tsfiles directly during development.@types/node— types for Node’s standard library.
Step 2 — Create tsconfig.json
npx tsc --init
Replace the generated file with a focused config:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noUncheckedIndexedAccess": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}
noUncheckedIndexedAccess makes array/object access return T | undefined,
forcing you to handle missing keys explicitly.
Step 3 — Write your first module
src/index.ts:
interface Greeting {
name: string;
punctuation?: string;
}
export function greet(input: Greeting): string {
const mark = input.punctuation ?? "!";
return `Hello, ${input.name}${mark}`;
}
console.log(greet({ name: "devnotes" }));
Step 4 — Path aliases for clean imports
Deep relative imports (../../../../lib/db) get unreadable fast. Add a paths
map to tsconfig.json and import from an alias instead:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
// now: import { db } from "@/lib/db";
TypeScript resolves the alias at compile time, and tsx handles it during
development. Node itself needs a resolver too — tools like tsx and bundlers
cover this, so you only hit friction if you run compiled output with plain
node.
Step 5 — Add build and dev scripts
In package.json:
{
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
}
}
npm run dev hot-reloads during development; npm run build typechecks and
emits plain JavaScript to dist/.
Step 6 — Editor integration
The whole point of a strict tsconfig.json is catching errors before you run
anything, and that happens in the editor. Open the project in VS Code (it
ships TypeScript support built in) and you get live red squiggles, hover types,
and go-to-definition — all driven by this same config. If errors don’t show,
run TypeScript: Select TypeScript Version and pick the workspace version,
so the editor uses your installed compiler instead of its bundled one.
Step 7 — Wire typechecking into CI
Make type errors fail the build:
npm run build
tsc with strict is your first line of defense — treat any error as a bug,
not a suggestion.
Troubleshooting
Cannot find modulewith ESM — with"module": "NodeNext", local imports need explicit.jsextensions (import { greet } from "./index.js").Object is possibly undefined— that’sstrict/noUncheckedIndexedAccessdoing its job; narrow with a guard or optional chaining.- Output directory is messy — confirm
rootDir/outDirare set sotscmirrors yoursrc/layout.
Summary
You have a strict, modern TypeScript project with a dev loop (tsx watch) and a
production build (tsc). The compiler is now guarding every commit.
For the editor side of this setup, see How to Set Up VS Code for Web Development; to make CI fail on type errors automatically, GitHub Actions for CI/CD wires npm run build into every push.
Comments
One comment per thread every 30 minutes · edits are unlimited.