Перейти к основному содержимому

TypeScript и Node.js

Разработчику Архитектору

Дальше: TypeORM · Асинхронность · Обработка ошибок · Форматы и подключение


Node.js выполняет скомпилированный JavaScript; TypeScript задаёт контракты для HTTP API, файлов, БД и доменной логики. DTO (Data Transfer Object) на границе сети отделяют JSON от доменной модели — см. паттерны. Один язык на frontend и backend упрощает общие типы — при дисциплине слоёв. Удобный порядок — первая программа, форматы и подключение, эта статья, TypeORM.

Runtime Node: раздел Node в JS. Монорепо — Экосистема и архитектура TypeScript.


TypeScript на backend

ВыгодаПример
Контракт APICreateUserDto и UserEntity — разные типы
Рефакторингпереименование поля ловит все handlers
CItsc --noEmit на каждый PR
Общие типыпакет @myorg/contracts в monorepo

TS не отменяет валидацию тела запроса: JSON из сети — unknown до проверки — Рекомендации по разработке на TypeScript.


Минимальный проект

mkdir my-api && cd my-api
npm init -y
npm i -D typescript @types/node tsx
npx tsc --init

tsconfig.json:

{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"outDir": "dist",
"rootDir": "src",
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}

package.json:

{
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsx watch src/index.ts",
"typecheck": "tsc --noEmit"
}
}

Разбор:


Слои — DTO, domain, persistence

Код ITЗагрузка примера кода…

Разбор:

  • DTO отражает тело запроса; domain — то, с чем работает сервис.
  • Поля БД (created_at) не протекают в API без маппера.

Сервис и Result

Код ITЗагрузка примера кода…

Подробнее Result — Обработка ошибок в TypeScript.


Handler в стиле Express

Код ITЗагрузка примера кода…

Разбор:

  • bodyunknown до guard.
  • Ответ типизирован union — клиент на TS может переиспользовать тип.

NestJS, Fastify — те же принципы DTO; декораторы — Декораторы в TypeScript.


Файлы — fs/promises


import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";

async function loadConfig(dir: string): Promise<Record<string, string>> {
const file = path.join(dir, "config.json");
const raw = await readFile(file, "utf8");
const data: unknown = JSON.parse(raw);
if (typeof data !== "object" || data === null) {
throw new Error("Invalid config");
}
return data as Record<string, string>;
}

Разбор:


Структура backend-проекта

src/
├── index.ts # bootstrap
├── app.ts # HTTP app
├── modules/
│ └── users/
│ ├── user.service.ts
│ ├── user.routes.ts
│ └── user.types.ts
└── shared/
├── errors.ts
└── types/
СлойОтветственность
routes / controllersHTTP, статусы
servicesбизнес-логика, Result
repositoriesБД — TypeORM

Переменные окружения

function requireEnv(name: string): string {
const v = process.env[name];
if (!v) throw new Error(`Missing env: ${name}`);
return v;
}

const port = Number(requireEnv("PORT"));

Для сложных конфигов — Zod-схема env — Рекомендации по разработке на TypeScript.


CI

- run: npm ci
- run: npm run typecheck
- run: npm test
- run: npm run build

Разбор:


Частые ошибки

ОшибкаЧто делать
req.body as Dtotype guard
Один тип на DTO и ORM entityразделить + mapper
CommonJS/import без .jsNodeNext + расширение в import
Нет @types/nodedevDependency
any в middlewareтипизировать Request augmentation

Практика

  1. Поднимите API с одним POST /users и guard тела.
  2. Разделите CreateUserDto, User, ответ API.
  3. Добавьте typecheck в CI.
  4. Прочитайте файл конфига через fs/promises с unknown + проверкой.
  5. Верните 409 при conflict из ServiceResult.

Смежные статьи