TypeScript в Node.js лучше всего понимать через маленький API. Не через абстрактные типы, а через задачу: поднять сервер, вернуть JSON, описать форму данных и поймать ошибку до запуска
В этом уроке сделаем Express API с двумя endpoint-ами: /health и /lessons. Для разработки будем запускать TypeScript через tsx, а типы проверять отдельной командой tsc --noEmit. Это важное разделение: runner удобен для запуска, но проверку типов лучше держать явной
- Что получится в конце
- Создаем проект
- Настраиваем package.json
- tsconfig.json
- Первый сервер
- Добавляем тип Lesson
- Проверка типов
- Почему не только tsx
- Request params на маленьком примере
- Частые ошибки
- Мини-задание
- Что может быть еще интересно по этой теме
- Нужно ли компилировать Node.js TypeScript в JavaScript?
- Express сам поставляет типы?
- Почему params.id строка?
- Можно ли сразу подключать базу данных?
- Что почитать дальше по TypeScript
Что получится в конце
Проект:
ts-express-api/
├─ src/
│ └─ server.ts
├─ package.json
└─ tsconfig.json
API:
GET http://localhost:3000/health
GET http://localhost:3000/lessons
Ответ /lessons:
[
{
"id": 1,
"title": "TypeScript в Node.js",
"minutes": 35
}
]
Создаем проект
Нужен Node.js. Express в актуальной документации просит Node.js 18 или выше, так что проверь версию:
node -v
Создай проект:
mkdir ts-express-api
cd ts-express-api
npm init -y
Установи Express:
npm install express
И dev-зависимости:
npm install -D typescript tsx @types/node @types/express
Здесь:
typescript— компилятор и проверка типов;tsx— удобный запуск.tsфайлов в разработке;@types/node— типы Node.js API;@types/express— типы Express.
Настраиваем package.json
Добавь scripts:
{
"scripts": {
"dev": "tsx src/server.ts",
"typecheck": "tsc --noEmit"
}
}
dev запускает сервер. typecheck отдельно проверяет TypeScript. Это лучше, чем надеяться, что запуск всегда поймает все типовые проблемы
tsconfig.json
Создай tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src"]
}
Если у тебя другой шаблон проекта, настройки могут отличаться. Для этого урока важно, чтобы TypeScript видел src, работал в строгом режиме и понимал импорты
Первый сервер
Создай src/server.ts:
import express, { Request, Response } from "express";
const app = express();
const port = 3000;
app.get("/health", (_request: Request, response: Response) => {
response.json({
status: "ok",
service: "ts-express-api",
});
});
app.listen(port, () => {
console.log(`API started: http://localhost:${port}`);
});
Запусти:
npm run dev
Открой:
http://localhost:3000/health
Должен вернуться JSON со статусом ok
Добавляем тип Lesson
Теперь добавим данные:
type Lesson = {
id: number;
title: string;
minutes: number;
};
const lessons: Lesson[] = [
{
id: 1,
title: "TypeScript в Node.js",
minutes: 35,
},
{
id: 2,
title: "Express API",
minutes: 25,
},
];
И endpoint:
app.get("/lessons", (_request: Request, response: Response) => {
response.json(lessons);
});
Полный файл:
import express, { Request, Response } from "express";
type Lesson = {
id: number;
title: string;
minutes: number;
};
const lessons: Lesson[] = [
{ id: 1, title: "TypeScript в Node.js", minutes: 35 },
{ id: 2, title: "Express API", minutes: 25 },
];
const app = express();
const port = 3000;
app.get("/health", (_request: Request, response: Response) => {
response.json({ status: "ok", service: "ts-express-api" });
});
app.get("/lessons", (_request: Request, response: Response) => {
response.json(lessons);
});
app.listen(port, () => {
console.log(`API started: http://localhost:${port}`);
});
Проверь:
http://localhost:3000/lessons
Проверка типов
В другом окне терминала запусти:
npm run typecheck
Если ошибок нет, команда завершится спокойно
Теперь сломай данные:
const lessons: Lesson[] = [
{ id: 1, title: "TypeScript в Node.js", minute: 35 },
];
TypeScript должен подсветить, что minute не существует в Lesson, а minutes отсутствует
Это и есть практическая польза TypeScript в backend-коде: форма данных проверяется до запуска
Почему не только tsx
tsx удобен: он быстро запускает .ts файл. Но важно помнить: runner может запускать код без полноценной проверки типов. Поэтому в проекте нужна отдельная команда:
npm run typecheck
Перед публикацией, деплоем или коммитом лучше выполнять ее явно
Request params на маленьком примере
Добавим endpoint с id:
app.get("/lessons/:id", (request: Request, response: Response) => {
const id = Number(request.params.id);
const lesson = lessons.find((item) => item.id === id);
if (!lesson) {
response.status(404).json({ error: "Lesson not found" });
return;
}
response.json(lesson);
});
request.params.id приходит строкой, даже если в URL написано /lessons/1. Поэтому мы явно делаем Number(...)
Это частая ошибка: считать, что параметр маршрута уже число. TypeScript помогает помнить о границе между HTTP и внутренними типами
Частые ошибки
Первая ошибка — не ставить @types/node и @types/express. Тогда TypeScript хуже понимает Node.js и Express API
Вторая ошибка — запускать tsx и думать, что типы точно проверены. Держи tsc --noEmit отдельной командой
Третья ошибка — забывать, что данные из URL и body приходят извне. Даже если внутри проекта есть тип Lesson, входные данные все равно нужно проверять
Четвертая ошибка — смешивать разные настройки модулей из чужих примеров. Если import/export ломается, проверь module, moduleResolution и "type" в package.json
Мини-задание
Добавь endpoint:
GET /lessons/:id
Требования:
- если урок есть, вернуть его JSON;
- если урока нет, вернуть
404; request.params.idпреобразовать в number;- запустить
npm run typecheck.
Потом специально замени minutes на minute в одном уроке и посмотри, где TypeScript поймает ошибку
Что может быть еще интересно по этой теме
Нужно ли компилировать Node.js TypeScript в JavaScript?
Для разработки можно использовать runner вроде tsx. Для продакшена часто делают сборку или используют настроенный runtime-процесс. Главное — не забывать отдельную проверку типов
Express сам поставляет типы?
Обычно для TypeScript ставят @types/express. Это дает типы Request, Response и подсказки по API
Почему params.id строка?
Потому что URL — текст. Express не знает, что ты хотел число, пока ты сам не преобразуешь значение
Можно ли сразу подключать базу данных?
Можно, но для первого урока лучше сначала понять сервер, маршруты и типы ответа. Базу логичнее добавить отдельным материалом
Что почитать дальше по TypeScript
- tsconfig.json простыми словами: strict, target и module — чтобы понимать настройки проекта.
- Generics в TypeScript: первый пример без головоломок — пригодится для типизации API-ответов.
- Ошибки TypeScript: any, unknown, never и strict — чтобы не выключать strict при первой проблеме.
- TypeScript и JavaScript: в чем разница простыми словами — если хочется закрепить, где TypeScript заканчивается и начинается runtime.



