Как написать REST API на TypeScript за 30 минут: на практике

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему TypeScript и REST API — идеальная пара для быстрой разработки

Современная веб-разработка требует скорости и надёжности. Если вам нужно быстро создать бэкенд для мобильного приложения, веб-сервиса или микросервисной архитектуры, связка TypeScript + Node.js + Express — один из самых эффективных инструментов. TypeScript добавляет строгую типизацию в JavaScript, что позволяет отлавливать ошибки на этапе компиляции, а не в рантайме. Это особенно ценно при построении REST API, где каждый эндпоинт должен чётко знать, какие данные он принимает и возвращает.

В этой статье мы за 30 минут создадим полноценный REST API для управления списком задач (To-Do List). Вы научитесь настраивать проект, писать контроллеры, маршруты и middleware. Всё — на практических примерах с готовым кодом.

Подготовка и настройка проекта (5 минут)

Первым делом создадим структуру проекта и установим зависимости.

mkdir todo-api
cd todo-api
npm init -y
npm install express cors uuid
npm install -D typescript @types/express @types/cors @types/uuid ts-node nodemon

Создайте файл tsconfig.json в корне проекта:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

Добавьте скрипты в package.json:

"scripts": {
  "dev": "nodemon --exec ts-node src/index.ts",
  "build": "tsc",
  "start": "node dist/index.js"
}

Создайте папку src и файл index.ts — точку входа.

Базовая структура папок

  • src/index.ts — точка входа, запуск сервера
  • src/routes/tasks.ts — маршруты для задач
  • src/controllers/taskController.ts — логика обработки запросов
  • src/models/task.ts — интерфейсы и типы
  • src/middleware/errorHandler.ts — обработка ошибок (опционально)

Создание модели данных и интерфейсов (5 минут)

Определим типы данных для нашей задачи. Создайте файл src/models/task.ts:

export interface Task {
  id: string;
  title: string;
  completed: boolean;
  createdAt: Date;
}

export type CreateTaskDto = Pick<Task, 'title'>;
export type UpdateTaskDto = Partial<Pick<Task, 'title' | 'completed'>>;

Здесь мы используем Pick и Partial — встроенные утилитарные типы TypeScript. CreateTaskDto принимает только заголовок, а UpdateTaskDto — опциональные поля для частичного обновления.

Написание контроллеров и маршрутов (15 минут)

Теперь самое интересное — логика приложения. Создадим временное хранилище задач в памяти (in-memory storage). Для продакшена его нужно заменить на базу данных.

Контроллер задач

Файл src/controllers/taskController.ts:

import { Request, Response } from 'express';
import { v4 as uuidv4 } from 'uuid';
import { Task, CreateTaskDto, UpdateTaskDto } from '../models/task';

// Временное хранилище
let tasks: Task[] = [];

// GET /api/tasks
export const getAllTasks = (req: Request, res: Response) => {
  res.json(tasks);
};

// GET /api/tasks/:id
export const getTaskById = (req: Request, res: Response) => {
  const task = tasks.find(t => t.id === req.params.id);
  if (!task) {
    return res.status(404).json({ message: 'Задача не найдена' });
  }
  res.json(task);
};

// POST /api/tasks
export const createTask = (req: Request, res: Response) => {
  const { title } = req.body as CreateTaskDto;
  if (!title || typeof title !== 'string') {
    return res.status(400).json({ message: 'Поле title обязательно и должно быть строкой' });
  }

  const newTask: Task = {
    id: uuidv4(),
    title,
    completed: false,
    createdAt: new Date()
  };
  tasks.push(newTask);
  res.status(201).json(newTask);
};

// PUT /api/tasks/:id
export const updateTask = (req: Request, res: Response) => {
  const { id } = req.params;
  const updates = req.body as UpdateTaskDto;
  const taskIndex = tasks.findIndex(t => t.id === id);

  if (taskIndex === -1) {
    return res.status(404).json({ message: 'Задача не найдена' });
  }

  tasks[taskIndex] = { ...tasks[taskIndex], ...updates };
  res.json(tasks[taskIndex]);
};

// DELETE /api/tasks/:id
export const deleteTask = (req: Request, res: Response) => {
  const { id } = req.params;
  const taskIndex = tasks.findIndex(t => t.id === id);

  if (taskIndex === -1) {
    return res.status(404).json({ message: 'Задача не найдена' });
  }

  tasks.splice(taskIndex, 1);
  res.status(204).send();
};

Маршруты

Файл src/routes/tasks.ts:

import { Router } from 'express';
import {
  getAllTasks,
  getTaskById,
  createTask,
  updateTask,
  deleteTask
} from '../controllers/taskController';

const router = Router();

router.get('/', getAllTasks);
router.get('/:id', getTaskById);
router.post('/', createTask);
router.put('/:id', updateTask);
router.delete('/:id', deleteTask);

export default router;

Сборка в index.ts

Теперь соединим всё в главном файле src/index.ts:

import express from 'express';
import cors from 'cors';
import taskRoutes from './routes/tasks';

const app = express();
const PORT = process.env.PORT || 3000;

// Middleware
app.use(cors());
app.use(express.json());

// Маршруты
app.use('/api/tasks', taskRoutes);

// Запуск сервера
app.listen(PORT, () => {
  console.log(`Сервер запущен на http://localhost:${PORT}`);
});

Запуск и тестирование (5 минут)

В терминале выполните команду:

npm run dev

Сервер запустится на http://localhost:3000. Протестируем API через curl

Блоги

Рекомендаций по книгам