TypeScript в Node.js: первый Express API

TypeScript в Node.js лучше всего понимать через маленький API. Не через абстрактные типы, а через задачу: поднять сервер, вернуть JSON, описать форму данных и поймать ошибку до запуска

В этом уроке сделаем Express API с двумя endpoint-ами: /health и /lessons. Для разработки будем запускать TypeScript через tsx, а типы проверять отдельной командой tsc --noEmit. Это важное разделение: runner удобен для запуска, но проверку типов лучше держать явной

Что получится в конце

Проект:

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

Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x