Структура Node-проекта и правила разработки
Конвенции структуры Node-проекта
Node.js не навязывает одну схему папок — в отличие от NestJS или Rails. Это свобода и риск: через полгода в server.js оказываются маршруты, SQL, валидация и отправка писем в одном файле. Устойчивые команды фиксируют конвенции: где лежит код, как называются слои, что не коммитить.
Команды npm — 265.md. Runtime и модули — 26.md. Express и Router — 263.md.
Типовое дерево каталогов
Подходит для REST API и учебного fullstack:
notes-api/
├── package.json
├── package-lock.json
├── .gitignore
├── .env.example # шаблон без секретов
├── README.md
├── node_modules/ # не в Git
├── public/ # статика (если отдаёте с API)
├── src/
│ ├── app.js # создание express(), middleware
│ ├── server.js # listen + graceful shutdown
│ ├── routes/
│ ├── controllers/
│ ├── services/
│ ├── middleware/
│ ├── config/
│ └── utils/
└── tests/
Роли каталогов
| Папка | Назначение |
|---|---|
src/ | Исходники, которые вы пишете и ревьюите |
routes/ | Связка URL + метод HTTP → контроллер |
controllers/ | HTTP-слой: статус, заголовки, вызов сервиса |
services/ | Бизнес-логика без req/res |
middleware/ | Общие обработчики (лог, auth, ошибки) |
config/ | Чтение process.env, валидация конфига |
utils/ | Чистые функции без доменного смысла |
public/ | Статика как есть (favicon, robots), без секретов |
tests/ | Unit и integration (supertest) |
dist/ или build/ | Результат tsc/сборки — генерируется, часто в .gitignore |
Во frontend-части monorepo рядом могут быть components/, icons/ — в чистом API они не обязательны.
package.json как контракт проекта
{
"name": "notes-api",
"version": "1.0.0",
"type": "module",
"main": "src/server.js",
"engines": {
"node": ">=20.0.0"
},
"scripts": {
"dev": "node --watch src/server.js",
"start": "node src/server.js",
"test": "node --test"
},
"dependencies": {
"express": "^4.19.2"
},
"devDependencies": {
"nodemon": "^3.1.0"
}
}
| Поле | Зачем |
|---|---|
engines | Предупреждение при старой Node у коллеги |
type: "module" | ESM по умолчанию в .js |
main | Точка входа для require('your-pkg') при публикации |
exports | Тонкий контроль импортов в библиотеках |
Дополнительно в команде:
.nvmrcс одной строкой20— дляnvm use;- одинаковая major Node в CI и локально.
Полный разбор npm-полей и lock — 265.md.
Разделение app и server
Паттерн, который упрощает тесты:
// src/app.js — экспортирует приложение без listen
import express from 'express';
const app = express();
// middleware, routes…
export default app;
// src/server.js — только запуск процесса
import app from './app.js';
const PORT = process.env.PORT || 3000;
const server = app.listen(PORT, () => {
console.log(`Listening ${PORT}`);
});
// graceful shutdown — см. 267.md
export { server };
Supertest подключается к app без реального порта — 33.md.
Переменные окружения
| Файл | В Git? | Содержимое |
|---|---|---|
.env | Нет | Секреты и локальные порты |
.env.example | Да | Ключи без значений: DATABASE_URL= |
config/index.js | Да | Чтение process.env с дефолтами для dev |
// src/config/index.js
const port = Number(process.env.PORT) || 3000;
const dbUrl = process.env.DATABASE_URL;
if (!dbUrl && process.env.NODE_ENV === 'production') {
throw new Error('DATABASE_URL is required');
}
export { port, dbUrl };
Правила:
- пароли и API-ключи никогда в
public/и не в коде; - в production секреты из платформы (Render, Docker secrets, Vault), не из
.envв образе.
Правила для команды (чек-лист)
Зафиксируйте в README или CONTRIBUTING.md:
- Один стиль модулей — ESM или CJS во всём репозитории, не смешивать без причины.
- Запуск только через
npm run— команды из README, а не одноразовые подсказки в чате. - Именование —
camelCaseдля файлов утилит илиkebab-caseдля routes; главное — единообразие. - Импорты — алиасы (
@/services/...) черезimportsвpackage.jsonили tsconfig, если проект растёт. - Ошибки API — единый JSON
{ error, code? }— см. 263.md. - Логи — структурированные (
pino), не толькоconsole.logв production. - Мёртвый код — удалять неиспользуемые папки; не копить
old/,backup/.
Backend-only vs fullstack
| Тип | Особенности |
|---|---|
| API-only | src/ + tests/, без public/ или с минимальной статикой |
| SSR / шаблоны | views/, templates/ |
| Monorepo | apps/api, apps/web, корневой package.json с workspaces |
Связка React на :5173 и API на :3000 — 264.md.
Антипаттерны
| Плохо | Лучше |
|---|---|
Весь код в server.js на 800 строк | Router + services |
| SQL внутри route handler | TaskModel / repository |
Коммит node_modules | lock + npm ci |
| Секреты в репозитории | .env в .gitignore |
Синхронный readFileSync на каждый запрос | fs/promises или потоки — 268.md |
Эволюция учебного API "Заметки"
| Этап | Структура |
|---|---|
| 1 | Один server.js, массив в памяти — 262.md |
| 2 | routes/, middleware/errorHandler — 263.md |
| 3 | services/ + SQLite/Prisma |
| 4 | config/, миграции, tests/ с supertest |
Связанные материалы
- npm — команды
- CLI, Docker, production
- Встроенные модули fs и http
- Справочник: структура и package.json
HTTP-статусы и заголовки — HTTP как основа веб-интеграций.