TypeScript с Node.js

Почему стоит использовать TypeScript с Node.js?

TypeScript добавляет статическую типизацию в разработку на Node.js, обеспечивая лучшее инструментальное обеспечение, улучшая качество кода и опыт разработчиков.

Основные преимущества:

  • Безопасность типов для кода JavaScript
  • Улучшенная поддержка среды разработки с автозавершением
  • Раннее обнаружение ошибок в процессе разработки
  • Повышение удобочитаемости и документации кода
  • Упрощенный рефакторинг

Предварительные условия: установите последнюю версию Node.js LTS (рекомендуется v18+) и npm.

Проверьте версии командами: node -v и npm -v.


Настройка проекта TypeScript для Node.js

Этот раздел описывает процесс создания нового проекта Node.js, настроенного для TypeScript.

Примечание: Во время разработки вы пишете код на TypeScript (.ts) и компилируете его в JavaScript (.js) для запуска в рабочей среде.

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.js
  • npx 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/ES
  • sourceMap: генерирует карты для отладки скомпилированного кода

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 для поддержания высокого качества кода.