Введение: почему 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