JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Сборка мини-проекта: планирование, разбор структуры

Сборка мини-проекта: планирование, разбор структуры

Модуль 4: Node.js, Next.js и Angular
6 уровень , 9 лекция
Открыта

1. Планирование мини-проекта

Когда вы только начинаете учить Node.js и Express, всё кажется простым: один файл, пара маршрутов, пару строчек логики — и готово. Но как только проект становится чуть сложнее, код начинает разрастаться, и вы внезапно обнаруживаете, что у вас в файле app.js уже 400 строк, а найти нужную функцию — как искать иголку в стоге сена.

Правильная структура проекта — это как аккуратно разложенные инструменты в ящике: всё на своих местах, каждый элемент отвечает за свою задачу, и любой новый разработчик быстро разберётся, что где лежит.

Давайте соберём мини-проект — REST API для списка задач (todo-list). Это классика: проект достаточно простой, чтобы не утонуть в деталях, но достаточно интересный, чтобы отработать все основные паттерны архитектуры Express.

Что должно уметь наше приложение:

  • Получать список задач (GET /tasks)
  • Добавлять новую задачу (POST /tasks)
  • Получать задачу по id (GET /tasks/:id)
  • Обновлять задачу (PUT /tasks/:id)
  • Удалять задачу (DELETE /tasks/:id)

Для простоты все задачи будем хранить в памяти (в массиве), чтобы не отвлекаться на базы данных.

2. Основные папки и файлы

Вот типовая структура Express-проекта для нашего мини-приложения:


my-todo-app/
├── app.js
├── package.json
├── routes/
│   └── tasks.js
├── controllers/
│   └── tasksController.js
├── services/
│   └── tasksService.js
├── models/
│   └── task.js
├── utils/
│   └── idGenerator.js
└── README.md

Что где лежит:

  • app.js — точка входа, инициализация Express, подключение middleware и маршрутов.
  • routes/ — файлы маршрутов (роутеры), которые принимают запросы и передают их контроллерам.
  • controllers/ — "дирижёры", которые принимают запрос от роутера, вызывают нужные сервисы и формируют ответ.
  • services/ — бизнес-логика, работа с данными (например, массивом задач).
  • models/ — описание структуры данных (например, класс Task).
  • utils/ — вспомогательные функции (например, генератор уникальных id).
  • README.md — краткое описание проекта.

3. Разбираем структуру по кусочкам

app.js — точка входа

Здесь мы создаём приложение Express, подключаем middleware, маршруты и запускаем сервер.


// app.js
const express = require('express');
const tasksRouter = require('./routes/tasks');

const app = express();
const PORT = 3000;

// Middleware для парсинга JSON
app.use(express.json());

// Подключаем роуты
app.use('/tasks', tasksRouter);

// Обработка несуществующих маршрутов
app.use((req, res) => {
  res.status(404).json({ error: 'Not found' });
});

// Запуск сервера
app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

Комментарий:
Всё максимально просто и прозрачно — никакой логики внутри app.js, только настройка и запуск.

routes/tasks.js — маршруты задач

Роутер принимает HTTP-запросы и вызывает соответствующие методы контроллера.


// routes/tasks.js
const express = require('express');
const router = express.Router();
const tasksController = require('../controllers/tasksController');

// Получить список всех задач
router.get('/', tasksController.getAllTasks);

// Добавить новую задачу
router.post('/', tasksController.createTask);

// Получить задачу по id
router.get('/:id', tasksController.getTaskById);

// Обновить задачу по id
router.put('/:id', tasksController.updateTask);

// Удалить задачу по id
router.delete('/:id', tasksController.deleteTask);

module.exports = router;

Комментарий:
Роутер ничего не знает о внутренней логике — он просто делегирует задачи контроллеру.

controllers/tasksController.js — логика обработки запросов

Контроллеры принимают запрос, вызывают сервисы, формируют ответ.


// controllers/tasksController.js
const tasksService = require('../services/tasksService');

// Получить все задачи
exports.getAllTasks = (req, res) => {
  const tasks = tasksService.getAll();
  res.json(tasks);
};

// Добавить задачу
exports.createTask = (req, res) => {
  const { title } = req.body;
  if (!title) {
    return res.status(400).json({ error: 'Title is required' });
  }
  const newTask = tasksService.create(title);
  res.status(201).json(newTask);
};

// Получить задачу по id
exports.getTaskById = (req, res) => {
  const id = req.params.id;
  const task = tasksService.getById(id);
  if (!task) {
    return res.status(404).json({ error: 'Task not found' });
  }
  res.json(task);
};

// Обновить задачу
exports.updateTask = (req, res) => {
  const id = req.params.id;
  const { title, completed } = req.body;
  const updatedTask = tasksService.update(id, { title, completed });
  if (!updatedTask) {
    return res.status(404).json({ error: 'Task not found' });
  }
  res.json(updatedTask);
};

// Удалить задачу
exports.deleteTask = (req, res) => {
  const id = req.params.id;
  const deleted = tasksService.remove(id);
  if (!deleted) {
    return res.status(404).json({ error: 'Task not found' });
  }
  res.json({ message: 'Task deleted' });
};

Комментарий:
Контроллер не занимается хранением или обработкой данных — только принимает запрос, вызывает сервис и возвращает ответ.

services/tasksService.js — бизнес-логика

Здесь вся работа с данными: хранение, поиск, изменение задач.


// services/tasksService.js
const Task = require('../models/task');
const { generateId } = require('../utils/idGenerator');

let tasks = []; // Здесь живёт наш список задач (в памяти, без БД)

// Получить все задачи
exports.getAll = () => tasks;

// Получить задачу по id
exports.getById = (id) => tasks.find(task => task.id === id);

// Создать новую задачу
exports.create = (title) => {
  const newTask = new Task(generateId(), title, false);
  tasks.push(newTask);
  return newTask;
};

// Обновить задачу
exports.update = (id, { title, completed }) => {
  const task = tasks.find(t => t.id === id);
  if (!task) return null;
  if (title !== undefined) task.title = title;
  if (completed !== undefined) task.completed = completed;
  return task;
};

// Удалить задачу
exports.remove = (id) => {
  const index = tasks.findIndex(t => t.id === id);
  if (index === -1) return false;
  tasks.splice(index, 1);
  return true;
};

Комментарий:
Здесь можно было бы подключить базу данных, но для мини-проекта достаточно массива.

models/task.js — описание задачи

Модель — просто класс, описывающий структуру объекта.


// models/task.js
class Task {
  constructor(id, title, completed = false) {
    this.id = id;
    this.title = title;
    this.completed = completed;
  }
}

module.exports = Task;

Комментарий:
Такой подход пригодится, если вы захотите добавить дополнительные поля (например, дату создания).

utils/idGenerator.js — генератор уникальных id

Вспомогательная функция для генерации id (чтобы не было задач с одинаковыми id).


// utils/idGenerator.js
let currentId = 1;

function generateId() {
  return (currentId++).toString();
}

module.exports = { generateId };

Комментарий:
В реальных проектах используют UUID или базы данных, но для мини-проекта и такой вариант сойдёт.

4. Как всё это работает вместе?

Вот схема (блок-схема — почти UML, но без угрозы вашему психическому здоровью):


[Клиент (Postman/браузер)]
        |
        v
    [Маршрут /tasks/:id] <--- routes/tasks.js
        |
        v
[Контроллер tasksController.js]
        |
        v
[Сервис tasksService.js]
        |
        v
 [Массив задач/Task]
  • Клиент отправляет запрос на сервер.
  • Роутер определяет, какой контроллер должен обработать запрос.
  • Контроллер вызывает соответствующий метод сервиса.
  • Сервис работает с моделями и возвращает результат контроллеру.
  • Контроллер отправляет ответ клиенту.

5. Как запускать и тестировать мини-проект

  1. Создайте папки и файлы согласно структуре выше.
  2. Инициализируйте проект:
    npm init -y
    npm install express
    
  3. Запустите сервер:
    node app.js
    
  4. Используйте Postman, curl или браузер для тестирования API:
    • GET http://localhost:3000/tasks — получить все задачи
    • POST http://localhost:3000/tasks с телом { "title": "Купить хлеб" } — добавить задачу
    • GET http://localhost:3000/tasks/1 — получить задачу по id
    • PUT http://localhost:3000/tasks/1 с телом { "completed": true } — отметить задачу как выполненную
    • DELETE http://localhost:3000/tasks/1 — удалить задачу

Как расширять такой проект?

  • Добавьте авторизацию (например, через middleware).
  • Подключите базу данных (MongoDB, PostgreSQL).
  • Реализуйте валидацию данных (например, через библиотеку joi или zod).
  • Добавьте обработку ошибок через отдельный middleware.
  • Организуйте тесты (например, с помощью Jest или Mocha).
  • Разделите сервисы по разным сущностям, если задач станет больше.

6. Типичные ошибки при структурировании Express-проекта

Ошибка №1: Вся логика в одном файле.
Это соблазнительно для маленьких проектов, но очень быстро приводит к хаосу. Даже если приложение маленькое — старайтесь сразу разделять код на модули.

Ошибка №2: Роутеры содержат бизнес-логику.
Роутер — это диспетчер, он не должен заниматься обработкой данных. Если вся логика в роутере, потом будет сложно её тестировать и переиспользовать.

Ошибка №3: Контроллеры делают всё подряд.
Контроллер — не сервис. Его задача — принять запрос, вызвать сервис, вернуть ответ. Если контроллер начинает работать с массивом задач напрямую — это тревожный звоночек.

Ошибка №4: Нет моделей данных.
Даже если вы не используете базу данных, лучше описывать структуру данных явно (например, через класс Task). Это повысит читаемость и упростит переход к реальной БД.

Ошибка №5: Нет утилит/повторяется код.
Если вы пишете одну и ту же функцию (например, генератор id) в нескольких файлах — пора вынести её в utils.

Ошибка №6: Необработанные ошибки.
Если не обрабатывать ошибки (например, несуществующий id), приложение будет вести себя непредсказуемо. Лучше возвращать информативные коды и сообщения.

1
Задача
Модуль 4: Node.js, Next.js и Angular, 6 уровень, 9 лекция
Недоступна
Разделение маршрутов и бизнес-логики
Разделение маршрутов и бизнес-логики
1
Задача
Модуль 4: Node.js, Next.js и Angular, 6 уровень, 9 лекция
Недоступна
Организация структуры и подключение маршрутов
Организация структуры и подключение маршрутов
3
Опрос
Сборка мини-проекта, 6 уровень, 9 лекция
Недоступен
Сборка мини-проекта
Сборка мини-проекта
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ