Почему стоит использовать TypeScript с Node.js?
TypeScript добавляет статическую типизацию в разработку на Node.js, обеспечивая лучшее инструментальное обеспечение, улучшая качество кода и опыт разработчиков.
Основные преимущества:
- Безопасность типов для кода JavaScript
- Улучшенная поддержка среды разработки с автозавершением
- Раннее обнаружение ошибок в процессе разработки
- Повышение удобочитаемости и документации кода
- Упрощенный рефакторинг
Предварительные условия: установите последнюю версию Node.js LTS (рекомендуется v18+) и npm.
Проверьте версии командами: node -v и npm -v.
Настройка проекта TypeScript для Node.js
Этот раздел описывает процесс создания нового проекта Node.js, настроенного для TypeScript.
1. Создаем новый проект
Пример
mkdir my-ts-node-app
cd my-ts-node-app
npm init -y
npm install typescript @types/node --save-dev
npx tsc --init
Что делают команды:
typescript: добавляет компилятор TypeScript (tsc)@types/node: обеспечивает определения типов для Node.jsnpx tsc --init: создает файл конфигурацииtsconfig.json
2. Создаем папку исходников
Храните исходники в директории src, а итоговые файлы компиляции в dist.
mkdir src
# позже добавляем файлы типа: src/server.ts, src/middleware/auth.ts
3. Конфигурируем TypeScript
Редактируем автоматически созданный файл tsconfig.json:
Пример
{
"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: разделяют исходники (src) и скомпилированные файлы (dist)strict: включает наиболее строгую проверку типовesModuleInterop: улучшает совместимость с модулями CommonJS/ESsourceMap: генерирует карты для отладки скомпилированного кода
CommonJS vs ESM: В данном руководстве используется настройка module: "commonjs".
Если вы используете ESM (type: "module" в файле package.json), устанавливайте module: "nodenext" или "node16" и последовательно применяйте import/export.
4. Устанавливаем зависимости времени исполнения и разработки
Устанавливаем Express для обработки HTTP-запросов и полезные инструменты разработчика:
Пример
npm install express body-parser
npm install --save-dev ts-node nodemon @types/express
Внимание: Используйте ts-node и nodemon исключительно для разработки.
Для рабочей среды сначала выполните компиляцию с помощью tsc, а затем запускайте сервер Node на выходе JavaScript.
Структура проекта
Теперь структура вашего проекта будет следующей:
my-ts-node-app/
src/
server.ts
middleware/
auth.ts
entity/
User.ts
config/
database.ts
dist/
node_modules/
package.json
tsconfig.json
Простой пример сервера на TypeScript
Приведенный ниже пример демонстрирует минимальный сервер Express, написанный на TypeScript, включая интерфейс модели пользователя и несколько маршрутов.
Файл src/server.ts
import express, { Request, Response, NextFunction } from 'express';
import { json } from 'body-parser';
interface User {
id: number;
username: string;
email: string;
}
// Инициализация приложения Express
const app = express();
const PORT = process.env.PORT || 3000;
// Промежуточная программа
app.use(json());
// Имитация базы данных
const users: User[] = [
{ id: 1, username: 'user1', email: 'user1@example.com' },
{ id: 2, username: 'user2', email: 'user2@example.com' }
];
// Маршруты
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: 'Пользователь не найден' });
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: 'Требуется имя пользователя и адрес электронной почты' });
}
const newUser: User = {
id: users.length + 1,
username,
email
};
users.push(newUser);
res.status(201).json(newUser);
});
// Промежуточная программа обработки ошибок
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack);
res.status(500).json({ message: 'Что-то пошло не так!' });
});
// Запуск сервера
app.listen(PORT, () => {
console.log(`Сервер запущен на http://localhost:${PORT}`);
});
Что добавляет TypeScript:
- Типизированные интерфейсы
Request,ResponseиNextFunctionдля обработчиков Express - Интерфейс
User, гарантирующий структуру данных пользователей - Безопасный рефакторинг и лучшее автодополнение благодаря типизации параметров запросов и тел сообщений
Использование TypeScript с промежуточными программами Express
Промежуточные программы могут быть жестко типизированными.
Вы также можете расширить типы Express с помощью объявления слияния, чтобы хранить аутентифицированные данные пользователя в запросе.
Файл src/middleware/auth.ts
import { Request, Response, NextFunction } from 'express';
// Расширяем тип Request Express, чтобы включить пользовательские свойства
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: 'Отсутствует токен' });
}
try {
// В реальных приложениях проверяйте JWT-токены здесь
const decoded = { id: 1, role: 'admin' }; // Демо-код
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ message: 'Недействительный токен' });
}
};
export const authorize = (roles: string[]) => {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ message: 'Неавторизован' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ message: 'Нет доступа' });
}
next();
};
};
Используем промежуточные программы в маршрутах
// src/server.ts
import { authenticate, authorize } from './middleware/auth';
app.get('/api/admin', authenticate, authorize(['admin']), (req, res) => {
res.json({ message: `Привет администратор ${req.user?.id}` });
});
Использование TypeScript с базой данных (пример с TypeORM)
Можно применять ORM, такие как TypeORM, с использованием декораторов TypeScript для сопоставления классов с таблицами.
Перед началом:
- Установите пакеты:
npm install typeorm reflect-metadata pg(используйтеpgдля PostgreSQL) - Включите поддержку деклараций в
tsconfig.jsonпри работе с декораторами:{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } } - Подключите библиотеку
reflect-metadataединожды перед запуском приложения.
Файл 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: [],
});
Инициализируйте источник данных перед запуском сервера
// src/server.ts
import { AppDataSource } from './config/database';
AppDataSource.initialize()
.then(() => {
app.listen(PORT, () => console.log(`Сервер работает на http://localhost:${PORT}`));
})
.catch((err) => {
console.error('Ошибка инициализации БД', err);
process.exit(1);
});
Рабочий цикл разработки
1. Добавьте скрипты в package.json
Пример
{
"scripts": {
"build": "tsc",
"start": "node dist/server.js",
"dev": "nodemon --exec ts-node src/server.ts",
"watch": "tsc -w",
"test": "jest --config jest.config.js"
}
}
Примечание: Скрипт test является необязательным и предполагает использование Jest.
Если вы не используете Jest, этот скрипт можно пропустить.
2. Запустите приложение в режиме разработки
Пример
npm run dev
3. Сборка для рабочей среды
Пример
npm run build
npm start
Отладка с картами исходного кода
При включенной опции sourceMap в tsconfig.json вы сможете отлаживать скомпилированный код и переходить обратно к вашим .ts файлам.
node --enable-source-maps dist/server.js
Совет: Большинство сред разработки (включая VS Code) поддерживают отладку TypeScript с возможностью установки точек останова при включённых картах исходного кода.
Лучшие практики:
- Всегда определяйте типы для аргументов функций и возвращаемых значений
- Используйте интерфейсы для структур объектов
- Включите режим
strictвtsconfig.json - Используйте защитники типов для корректного контроля типов
- Пользуйтесь служебными типами TypeScript (Partial, Pick, Omit и др.)
- Храните ваши определения типов в файлах
.d.ts - Используйте перечисления или константные утверждения для фиксированных множеств значений
- Документируйте сложные типы при помощи комментариев JSDoc
- Предпочитайте переменные окружения для секретов и конфигураций; проверяйте их при старте
- Используйте
ts-node/nodemonтолько в разработке; собирайте проекты для рабочей среды - Рассмотрите использование ESLint + Prettier вместе с @typescript-eslint для поддержания высокого качества кода.