Loading lesson path
Concept visual
Why Use TypeScript with Node.js? TypeScript brings static typing to Node.js development, providing better tooling, improved code quality, and enhanced developer experience.
Formula
Install a recent Node.js LTS (v18 + recommended) and npm.
Verify with node - v and npm - v.Setting Up a TypeScript Node.js Project This section walks through creating a new Node.js project configured for TypeScript.
You write TypeScript (.ts ) during development and compile it to JavaScript (.js ) for Node.js to run in production.
Example mkdir my-ts-node-app cd my-ts-node-app npm init -y npm install typescript @types/node --save-dev npx tsc --init
typescript adds the TypeScript compiler ( tsc )
Formula
@types/node provides Node.js type definitions npx tsc -- init creates a tsconfig.json config fileand compiled output in dist/. mkdir src
Formula
# later add files like: src/server.ts, src/middleware/auth.ts{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}rootDir / outDir : keeps source ( src ) separate from build output ( dist ). strict : enables the safest type checking. esModuleInterop
Formula
: smoother interop with CommonJS/ES modules.sourceMap : generate maps for debugging compiled code.
This guide uses module: "commonjs". If you use ESM ( type: "module" in package.json ), set module: "nodenext" or node16, and use import / export consistently.
Example npm install express body-parser npm install --save-dev ts-node nodemon @types/express
Formula
Use ts - node and nodemon only for development.
For production, compile with tsc and run Node on the JS output.Formula
my - ts - node - app/src/ server.ts middleware/ auth.ts entity/ User.ts config/ database.ts dist/ node_modules/ package.json tsconfig.json
This example shows a minimal Express server written in TypeScript, including a typed User model and a few routes.
src/server.ts import express, { Request, Response, NextFunction } from 'express';
import { json } from 'body-parser';
interface User {
id: number;
username: string;
email: string;
}
// Initialize Express app const app = express();
const PORT = process.env.PORT || 3000;
// Middleware app.use(json());Formula
// In - memory database const users: User[] = [{ id: 1, username: 'user1', email: 'user1@example.com' },
{ id: 2, username: 'user2', email: 'user2@example.com' }
];Formula
// Routes app.get('/api/users', (req: Request, res: Response) => {res.json(users);
});
app.get('/api/users/:id', (req: Request, res: Response) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).json({ message: 'User not found' });
res.json(user);
});
app.post('/api/users', (req: Request, res: Response) => {
const { username, email } = req.body;
if (!username || !email) {
return res.status(400).json({ message: 'Username and email are required' });
}
const newUser: User = {Formula
id: users.length + 1, username, email};
users.push(newUser);
res.status(201).json(newUser);
});
// Error handling middleware app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack);
res.status(500).json({ message: 'Something went wrong!' });
});
// Start server app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});Request, Response, and NextFunction for Express handlers. A User interface to guarantee the shape of user data. Safer refactoring and better autocompletion with typed route params and bodies.
Middleware can be strongly typed. You can also extend Express types via declaration merging to store authenticated user data on the request.
src/middleware/auth.ts import { Request, Response, NextFunction } from 'express';
// Extend the Express Request type to include custom properties declare global {
namespace Express {
interface Request {
user?: { id: number; role: string };
}
}
}
export const authenticate = (req: Request, res: Response, next: NextFunction) => {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ message: 'No token provided' });
}
try {
// In a real app, verify the JWT token here const decoded = { id: 1, role: 'admin' }; // Mock decoded token req.user = decoded;
next();
} catch (error) {
res.status(401).json({ message: 'Invalid token' });
}
};
export const authorize = (roles: string[]) => {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ message: 'Not authenticated' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ message: 'Not authorized' });
}
next();
};
};// src/server.ts import { authenticate, authorize } from './middleware/auth';
app.get('/api/admin', authenticate, authorize(['admin']), (req, res) => {
res.json({ message: `Hello admin ${req.user?.id}` });
});
TypeScript with Database (TypeORM Example)You can use ORMs like TypeORM with TypeScript decorators to map classes to tables.
Formula
npm install typeorm reflect - metadata pg(use pg for PostgreSQL). Enable in tsconfig.json when using decorators:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}Formula
Import reflect - metadata once at app startup.src/entity/User.ts import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
username: string;
@Column({ unique: true })
email: string;
@Column({ select: false })
password: string;
@Column({ default: 'user' })
role: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
src/config/database.ts import 'reflect-metadata';
import { DataSource } from 'typeorm';
import { User } from '../entity/User';
export const AppDataSource = new DataSource({
type: 'postgres', host: process.env.DB_HOST || 'localhost', port: parseInt(process.env.DB_PORT || '5432'), username: process.env.DB_USERNAME || 'postgres', password: process.env.DB_PASSWORD || 'postgres', database: process.env.DB_NAME || 'mydb', synchronize: process.env.NODE_ENV !== 'production', logging: false, entities: [User], migrations: [], subscribers: [],
});Initialize the Data Source before starting the server
// src/server.ts import { AppDataSource } from './config/database';
AppDataSource.initialize().then(() => {
app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));
}).catch((err) => {
console.error('DB init error', err);
process.exit(1);
});