Sitemap
Bhaskar Chowdary

Bhaskar Chowdary

I believe in There is No Problem Without Solution

JavaScript + Backend Bootcamp

Day 9 : Node.js + TypeScript Project Setup

9 min readJul 15, 2026

--

From today onward, all backend code in this bootcamp will use TypeScript.

Today you’ll learn how to create a professional Node.js project with:

  • Node.js
  • TypeScript
  • tsx
  • npm scripts
  • Environment variables
  • ES modules
  • Git and GitHub
  • Development and production builds
  • A scalable folder structure

By the end of Day 9, you’ll have the foundation for every upcoming backend project.

1. What Is Node.js?

Node.js is a JavaScript runtime that allows JavaScript and TypeScript applications to run outside the browser.

It is commonly used to build:

  • REST APIs
  • Web servers
  • Authentication systems
  • Real-time applications
  • Command-line tools
  • Database-backed applications

Node.js can now execute some TypeScript syntax directly, but its built-in type stripping does not use tsconfig.json and does not perform type checking. For this bootcamp, we’ll use tsx during development and tsc for type checking and production compilation. (Node.js)

2. Check Node.js and npm

Open the terminal:

node --version

Check npm:

npm --version

Install the current Node.js LTS release when starting the bootcamp.

3. Create the Project

mkdir day-9-node-typescript
cd day-9-node-typescript

Initialize npm:

npm init -y

This creates:

package.json

package.json stores project metadata, scripts, dependencies and development dependencies. (npm Docs)

4. Install TypeScript Tools

Install TypeScript, Node.js type definitions and tsx:

npm install --save-dev typescript tsx @types/node

These packages have different responsibilities:

  • typescript: type-checks and compiles TypeScript
  • tsx: executes TypeScript during development
  • @types/node: provides TypeScript definitions for Node.js APIs

Node.js officially documents tsx as an option for running TypeScript with broader TypeScript syntax support. However, tsx does not type-check the program, so tsc should still be run separately. (Node.js)

5. Why Use tsx Instead of ts-node?

Both tools can run TypeScript.

For this bootcamp, we’ll use:

tsx

because it provides:

  • Simple configuration
  • Fast TypeScript execution
  • ES module support
  • Watch mode
  • Good development experience

Run a file using:

npx tsx src/index.ts

Run it in watch mode:

npx tsx watch src/index.ts

Watch mode automatically reruns the application when an imported project file changes. (tsx.is)

6. Create the Project Structure

Create the following folders and files:

day-9-node-typescript/
│
├── src/
│ ├── config/
│ ├── controllers/
│ ├── routes/
│ ├── services/
│ ├── types/
│ ├── utils/
│ └── index.ts
│
├── dist/
├── .env
├── .env.example
├── .gitignore
├── package.json
├── package-lock.json
└── tsconfig.json

For Day 9, only src/index.ts is required.

The other folders prepare the project for Express, MongoDB and authentication later.

7. Create tsconfig.json

Run:

npx tsc --init

Replace the generated content with:

{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"rootDir": "./src",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"sourceMap": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}

moduleResolution: "NodeNext" makes TypeScript follow Node.js module resolution rules. Source maps create mappings between generated JavaScript and original TypeScript, which improves debugging. (TypeScript)

8. Understanding Important Compiler Options

target

"target": "ES2022"

Controls the JavaScript version produced by TypeScript.

module

"module": "NodeNext"

Configures TypeScript for Node.js module behavior.

rootDir

"rootDir": "./src"

The location of TypeScript source files.

outDir

"outDir": "./dist"

The location of compiled JavaScript.

strict

"strict": true

Enables TypeScript’s strict type-checking rules.

sourceMap

"sourceMap": true

Produces .js.map files for debugging compiled code. (TypeScript)

types

"types": ["node"]

Loads Node.js type declarations without automatically loading every installed declaration package.

9. Configure ES Modules

Open package.json and add:

"type": "module"

Example:

{
"name": "day-9-node-typescript",
"version": "1.0.0",
"type": "module",
"main": "dist/index.js"
}

This project will use modern ES module syntax:

import
export

instead of:

require()
module.exports

10. Create Your First Node.js TypeScript Program

Create:

src/index.ts

Add:

const appName = "TypeScript Backend Bootcamp";
const port = 3000;
console.log(`${appName} is starting...`);
console.log(`Port: ${port}`);

Run it:

npx tsx src/index.ts

Output:

TypeScript Backend Bootcamp is starting...
Port: 3000

11. Add npm Scripts

Update package.json:

{
"name": "day-9-node-typescript",
"version": "1.0.0",
"private": true,
"type": "module",
"main": "dist/index.js",
"scripts": {
"dev": "tsx watch src/index.ts",
"typecheck": "tsc --noEmit",
"build": "tsc",
"start": "node dist/index.js"
},
"devDependencies": {
"@types/node": "^24.0.0",
"tsx": "^4.0.0",
"typescript": "^5.0.0"
}
}

Do not manually copy the package versions above into an existing project. Your installed versions will be written automatically by npm.

The scripts field maps script names to commands that can be executed using npm run. Development-only tools such as TypeScript and tsx belong in devDependencies. (npm Docs)

12. Run Development Mode

npm run dev

This runs:

tsx watch src/index.ts

Whenever you save an imported source file, the application restarts automatically. (tsx.is)

13. Type-Check the Project

Run:

npm run typecheck

This executes:

tsc --noEmit

It checks the code without creating JavaScript output.

Example error:

const port: number = "3000";

TypeScript reports:

Type 'string' is not assignable to type 'number'.

Because tsx runs code without performing full TypeScript type checking, keeping a separate typecheck script is important. (Node.js)

14. Build the Project

Run:

npm run build

TypeScript compiles:

src/index.ts

into:

dist/index.js

Project structure after building:

day-9-node-typescript/
│
├── src/
│ └── index.ts
│
├── dist/
│ ├── index.js
│ └── index.js.map
│
├── package.json
└── tsconfig.json

tsx executes TypeScript during development, but it does not create the production JavaScript build. Compilation is handled by tsc. (tsx.is)

15. Run the Production Build

After building:

npm start

This executes:

node dist/index.js

Recommended workflow:

npm run typecheck
npm run build
npm start

16. Development vs Production

Development

npm run dev

Uses:

tsx watch

Benefits:

  • Runs TypeScript directly
  • Watches file changes
  • Restarts automatically
  • Faster development workflow

Production

npm run build
npm start

Uses:

tsc → JavaScript → Node.js

Benefits:

  • Runs compiled JavaScript
  • Detects type errors before deployment
  • Produces predictable output
  • Does not require the TypeScript source runner

17. Creating Modules

Create:

src/utils/greet.ts

Add:

export function greet(name: string): string {
return `Hello, ${name}`;
}

Update:

src/index.ts
import { greet } from "./utils/greet.js";
console.log(greet("Ravi"));

Notice the import:

"./utils/greet.js"

With NodeNext and compiled ES modules, relative imports should use the file extension that Node.js will execute after compilation.

18. Default Exports

Create:

src/config/app.ts
const appConfig = {
name: "Backend Bootcamp",
version: "1.0.0"
};
export default appConfig;

Import it:

import appConfig from "./config/app.js";
console.log(appConfig.name);

19. Importing Node.js Built-in Modules

Node.js built-in modules can use the node: prefix.

import path from "node:path";
const filePath = path.join(
process.cwd(),
"src",
"index.ts"
);
console.log(filePath);

Using node: makes it clear that the import comes from Node.js rather than an npm package.

20. Understanding process

Node.js provides the global process object.

console.log(process.version);
console.log(process.platform);
console.log(process.cwd());

Common properties:

  • process.env: environment variables
  • process.argv: command-line arguments
  • process.cwd(): current working directory
  • process.exit(): terminate the process
  • process.version: current Node.js version

21. Command-Line Arguments

Run:

npm run dev -- Ravi

Read the arguments:

const args = process.argv.slice(2);
const name = args[0] ?? "Guest";console.log(`Hello, ${name}`);

Output:

Hello, Ravi

22. Environment Variables

Environment variables store values that can change between environments.

Get Bhaskar Chowdary’s stories in your inbox

Join Medium for free to get updates from this writer.

Examples:

  • Server port
  • Database connection string
  • JWT secret
  • API keys
  • Application environment

Create:

.env

Add:

PORT=3000
NODE_ENV=development
APP_NAME=Backend Bootcamp

23. Loading .env Values

Modern Node.js supports loading an environment file using its --env-file option.

Update the development script:

"dev": "tsx watch --env-file=.env src/index.ts"

Update the production script:

"start": "node --env-file=.env dist/index.js"

Alternatively, a package such as dotenv can be introduced later when the project needs more configuration control.

24. Reading Environment Variables

const port = process.env.PORT;
const environment = process.env.NODE_ENV;
const appName = process.env.APP_NAME;
console.log({
port,
environment,
appName
});

Environment variable values are typed as:

string | undefined

because the value may not exist.

25. Safely Parse the Port

Avoid:

const port = Number(process.env.PORT);

If PORT is missing or invalid, this may produce NaN.

Use validation:

function getPort(value: string | undefined): number {
if (!value) {
return 3000;
}
  const port = Number(value);  if (!Number.isInteger(port) || port <= 0) {
throw new Error("PORT must be a positive integer");
}
return port;
}
const port = getPort(process.env.PORT);console.log(`Application running on port ${port}`);

26. Create a Type-Safe Config Module

Create:

src/config/env.ts
interface EnvironmentConfig {
port: number;
nodeEnv:
| "development"
| "test"
| "production";
appName: string;
}
function parseNodeEnv(
value: string | undefined
): EnvironmentConfig["nodeEnv"] {
if (
value === "development" ||
value === "test" ||
value === "production"
) {
return value;
}
return "development";
}
function parsePort(
value: string | undefined
): number {
const port = Number(value ?? 3000);
if (!Number.isInteger(port) || port <= 0) {
throw new Error(
"PORT must be a positive integer"
);
}
return port;
}
export const env: EnvironmentConfig = {
port: parsePort(process.env.PORT),
nodeEnv: parseNodeEnv(process.env.NODE_ENV),
appName:
process.env.APP_NAME ??
"TypeScript Backend"
};

Use it in src/index.ts:

import { env } from "./config/env.js";
console.log(`${env.appName} started`);
console.log(`Environment: ${env.nodeEnv}`);
console.log(`Port: ${env.port}`);

27. Create .env.example

Create:

.env.example

Add:

PORT=3000
NODE_ENV=development
APP_NAME=Backend Bootcamp

This file documents required environment variables without exposing real secrets.

Commit:

.env.example

Do not commit:

.env

28. Create .gitignore

Create:

.gitignore

Add:

# Dependencies
node_modules/
# Compiled output
dist/
# Environment variables
.env
.env.local
.env.*.local
# TypeScript build information
*.tsbuildinfo
# Logs
*.log
logs/
# Editor files
.vscode/
.idea/
# Operating system files
.DS_Store
Thumbs.db
# Test coverage
coverage/

Do not place .env.example inside .gitignore.

29. Initialize Git

git init

Check project files:

git status

Add files:

git add .

Create the first commit:

git commit -m "Initialize Node.js TypeScript project"

30. Connect to GitHub

Create an empty GitHub repository, then run:

git remote add origin YOUR_REPOSITORY_URL

Rename the current branch:

git branch -M main

Push:

git push -u origin main

Never push:

  • .env
  • Database passwords
  • JWT secrets
  • Private keys
  • API credentials

31. Add a Build Validation Script

Update package.json:

{
"scripts": {
"dev": "tsx watch --env-file=.env src/index.ts",
"typecheck": "tsc --noEmit",
"build": "tsc",
"start": "node --env-file=.env dist/index.js",
"check": "npm run typecheck && npm run build"
}
}

Run:

npm run check

This checks types before creating the production build.

32. Optional Clean Build

Install a cross-platform cleanup package:

npm install --save-dev rimraf

Update scripts:

{
"scripts": {
"clean": "rimraf dist",
"dev": "tsx watch --env-file=.env src/index.ts",
"typecheck": "tsc --noEmit",
"build": "npm run clean && tsc",
"start": "node --env-file=.env dist/index.js"
}
}

Now every build removes old compiled files first.

33. Final package.json

A clean setup looks like this:

{
"name": "day-9-node-typescript",
"version": "1.0.0",
"description": "Node.js and TypeScript bootcamp project",
"private": true,
"type": "module",
"main": "dist/index.js",
"scripts": {
"clean": "rimraf dist",
"dev": "tsx watch --env-file=.env src/index.ts",
"typecheck": "tsc --noEmit",
"build": "npm run clean && tsc",
"start": "node --env-file=.env dist/index.js",
"check": "npm run typecheck && npm run build"
},
"devDependencies": {
"@types/node": "^24.0.0",
"rimraf": "^6.0.0",
"tsx": "^4.0.0",
"typescript": "^5.0.0"
}
}

Allow npm to write the actual dependency versions when installing packages.

34. Final Project Structure

day-9-node-typescript/
│
├── src/
│ ├── config/
│ │ └── env.ts
│ ├── controllers/
│ ├── routes/
│ ├── services/
│ ├── types/
│ ├── utils/
│ │ └── greet.ts
│ └── index.ts
│
├── .env
├── .env.example
├── .gitignore
├── package.json
├── package-lock.json
└── tsconfig.json

After building:

dist/
├── config/
│ ├── env.js
│ └── env.js.map
├── utils/
│ ├── greet.js
│ └── greet.js.map
├── index.js
└── index.js.map

35. Mini Project — Typed Application Starter

src/types/app.types.ts

export type Environment =
| "development"
| "test"
| "production";
export interface ApplicationInfo {
name: string;
version: string;
environment: Environment;
port: number;
}

src/config/app.ts

import type {
ApplicationInfo,
Environment
} from "../types/app.types.js";
function getEnvironment(
value: string | undefined
): Environment {
if (
value === "development" ||
value === "test" ||
value === "production"
) {
return value;
}
return "development";
}
function getPort(
value: string | undefined
): number {
const port = Number(value ?? 3000);
if (!Number.isInteger(port) || port <= 0) {
throw new Error("Invalid server port");
}
return port;
}
export const application: ApplicationInfo = {
name:
process.env.APP_NAME ??
"Backend Bootcamp",
version: "1.0.0",
environment: getEnvironment(
process.env.NODE_ENV
),
port: getPort(process.env.PORT)
};

src/index.ts

import { application } from "./config/app.js";
function startApplication(): void {
console.log("--------------------------------");
console.log(`${application.name}`);
console.log(`Version: ${application.version}`);
console.log(
`Environment: ${application.environment}`
);
console.log(`Port: ${application.port}`);
console.log("--------------------------------");
}
try {
startApplication();
} catch (error: unknown) {
if (error instanceof Error) {
console.error(
`Startup failed: ${error.message}`
);
} else {
console.error(
"Startup failed due to an unknown error"
);
}
process.exit(1);
}

Run:

npm run dev

Example output:

--------------------------------
Backend Bootcamp
Version: 1.0.0
Environment: development
Port: 3000
--------------------------------

Common Beginner Mistakes

Running production with tsx

Avoid:

tsx src/index.ts

as your final deployment approach.

Use:

npm run build
npm start

Forgetting type checking

tsx runs TypeScript but does not perform full type checking.

Run:

npm run typecheck

before building or deploying. (Node.js)

Committing .env

Never commit sensitive values:

DATABASE_URL=...
JWT_SECRET=...
API_KEY=...

Commit .env.example instead.

Mixing CommonJS and ES modules

Avoid combining:

require()

with:

import

Use one module system consistently.

This bootcamp uses ES modules.

Importing TypeScript files incorrectly

With the compiled NodeNext setup, use:

import { greet } from "./utils/greet.js";

not:

import { greet } from "./utils/greet";

Depending on TypeScript types for runtime validation

This interface:

interface Config {
port: number;
}

does not validate environment variables while the application runs.

External values must still be checked and parsed.

Important Concepts Learned Today

✅ Node.js project setup
✅ npm and package.json
✅ TypeScript in Node.js
✅ tsx development runner
✅ Watch mode
✅ Node.js type definitions
✅ tsconfig.json for Node.js
✅ ES modules
✅ npm scripts
✅ Type checking
✅ Development and production builds
✅ Environment variables
✅ Type-safe configuration
✅ Folder structure
✅ .gitignore
✅ Git and GitHub
✅ Module imports and exports

Homework

Create a new Node.js TypeScript project with:

  1. src and dist directories
  2. Strict TypeScript configuration
  3. ES module support
  4. Development watch script
  5. Type-check script
  6. Production build script
  7. Environment variables
  8. Type-safe configuration module
  9. .gitignore
  10. Git repository with an initial commit

Practice Challenge

Create an application configuration system that reads:

APP_NAME=Task Manager API
PORT=4000
NODE_ENV=development

Expected output:

Task Manager API
Environment: development
Server port: 4000

Requirements:

  • Use an interface
  • Validate PORT
  • Use a literal union for NODE_ENV
  • Throw an error for invalid configuration
  • Handle startup errors using unknown

Day 10

Node.js Core Modules + HTTP Server

We’ll learn:

  • node:fs
  • node:path
  • node:os
  • node:events
  • node:http
  • Buffers
  • Streams
  • HTTP request and response objects
  • HTTP methods
  • Routes
  • Status codes
  • JSON responses

Mini Projects:

  • File Manager
  • Basic TypeScript API Server

--

--

Written by Bhaskar Chowdary

I believe in There is No Problem Without Solution