Полнофункциональный REST API-сервис для управления веб‑закладками тегами, поиском, и встроенным сокращателем ссылок с подсчётом кликов и историей переходов. В комплекте — документация Swagger, защита (Helmet, CORS, Rate Limit), валидаторы, тесты (Jest + Supertest) и Docker-окружение.
— Стартовый эндпоинт проверки: GET /api/health → { status: "ok" }
— Swagger UI: http://localhost:4000/api/docs
- Аутентификация по JWT: регистрация, вход, получение профиля
- CRUD для закладок
- Поиск закладок
- Теги
- Сокращение ссылок: генерация коротких кодов, публичные редиректы
- Статистика коротких ссылок: счётчик кликов и история с IP/User‑Agent
- Документация OpenAPI (Swagger UI)
- Безопасность: Helmet, CORS, rate limiting (конфигурируемый)
- Валидаторы входных данных (express‑validator)
- Юнит/интеграционные тесты (Jest + Supertest)
- Runtime: Node.js (ES Modules)
- Web‑фреймворк: Express 5
- БД: MongoDB (Mongoose)
- Аутентификация: JWT (jsonwebtoken)
- Хэширование паролей: bcryptjs
- Метаданные: axios + cheerio (OG‑теги)
- Валидация: express‑validator
- Безопасность: helmet, cors, express‑rate‑limit
- Документация: swagger‑jsdoc + swagger‑ui‑express
- Тестирование: Jest, Supertest
- Код‑стайл: ESLint, Prettier
- Контейнеризация: Docker, docker‑compose
app.js # Точка входа, конфиг middlewares/роутов, Swagger
config/db.js # Подключение к MongoDB
controllers/*.js # Логика эндпоинтов (auth, bookmarks, shortener, tags)
middleware/*.js # auth, validate, error handlers
models/*.js # Mongoose‑схемы (User, Bookmark, ShortLink)
routes/*.js # Маршрутизация и схема валидации (OpenAPI JSDoc)
utils/*.js # JWT helper, парсинг метаданных, генерация short code
docs/swagger.js # Конфиг swagger‑jsdoc
tests/*.test.js # Тесты
Dockerfile, docker-compose.yml # Контейнеры (API, Mongo, Mongo‑Express)
- Node.js 18+
- MongoDB 5+ (локально или в Docker)
Минимально нужны:
PORT— порт API (по умолчанию 4000)MONGODB_URI— строка подключения к MongoDBJWT_SECRET— секрет для подписи JWTJWT_EXPIRES_IN— срок жизни токена (например,7d)CORS_ORIGIN— разрешённый Origin для CORS (по умолчанию*)RATE_LIMIT_WINDOW_MS— окно rate‑limit в мс (по умолчанию 60000)RATE_LIMIT_MAX— макс. запросов за окно (по умолчанию 100)
В тестах значения подставляются из tests/setup.js.
- Установите зависимости:
npm i
-
Поднимите локальный MongoDB или укажите внешний
MONGODB_URIв.env. -
Запустите в дев‑режиме (автоперезапуск):
npm run dev
- Либо прод‑режим:
npm start
Проверьте здоровье: GET http://localhost:4000/api/health.
docker compose up --build
Сервисы:
- API:
http://localhost:4000 - Swagger UI:
http://localhost:4000/api/docs - Mongo Express (UI):
http://localhost:8081(логин/пароль изdocker-compose.yml)
Тесты используют реальную MongoDB по адресу из tests/setup.js:
mongodb://127.0.0.1:27017/bookmark_api_test. Убедитесь, что MongoDB запущена локально.
Запуск:
npm test
Покрытие:
npm run test:coverage
- Линт:
npm run lint - Форматирование:
npm run format
- Автоматически генерируется из JSDoc в файлах
routes/*.js - Открыть UI:
http://localhost:4000/api/docs
email(unique, index, required, lowercase, trim)password(required, minLength 6) — хэшируется в pre‑save (bcrypt)name,avatar(optional)- Индексы: email unique
- Методы:
matchPassword(candidate)→ boolean
url(required, string, trim)title,descriptiontags(array of string, index)userId(ObjectId → User, required, index)isFavorite(boolean, default false)meta(embedded):ogTitle,ogImage,ogDescription- Индексы:
{ userId: 1, url: 1 }unique — одна закладка на URL в рамках пользователя
shortCode(string, required, unique, index)originalUrl(string, required)bookmarkId(ObjectId → Bookmark, optional)userId(ObjectId → User, index)clicks(number, default 0)clickHistory(array): элементы сtimestamp,ipAddress,userAgentexpiresAt(Date, optional)- Индексы:
- TTL:
{ expiresAt: 1 }сexpireAfterSeconds: 0(частичный: еслиexpiresAtсуществует)
- TTL:
- JWT Bearer Token в заголовке:
Authorization: Bearer <token> - Токен выдаётся в
POST /api/auth/registerиPOST /api/auth/login - Защищённые маршруты используют middleware
authRequired
Ниже — краткие примеры cURL. Все защищённые эндпоинты требуют заголовок Authorization.
curl -i http://localhost:4000/api/health
— Регистрация
curl -X POST http://localhost:4000/api/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password123","name":"User"}'
— Логин
curl -X POST http://localhost:4000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password123"}'
— Текущий пользователь
curl http://localhost:4000/api/auth/me \
-H "Authorization: Bearer <TOKEN>"
— Создать закладку
curl -X POST http://localhost:4000/api/bookmarks \
-H "Authorization: Bearer <TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"url":"https://example.com",
"title":"Example Site",
"description":"A test bookmark",
"tags":["test","example"],
"isFavorite":false
}'
— Список (пагинация/фильтры)
curl "http://localhost:4000/api/bookmarks?limit=20&page=1&tag=test&favorite=true&q=example" \
-H "Authorization: Bearer <TOKEN>"
— Поиск
curl "http://localhost:4000/api/bookmarks/search?q=python" \
-H "Authorization: Bearer <TOKEN>"
— Получить по id
curl http://localhost:4000/api/bookmarks/<BOOKMARK_ID> \
-H "Authorization: Bearer <TOKEN>"
— Обновить
curl -X PUT http://localhost:4000/api/bookmarks/<BOOKMARK_ID> \
-H "Authorization: Bearer <TOKEN>" \
-H "Content-Type: application/json" \
-d '{"title":"Updated Title","isFavorite":true}'
— Удалить
curl -X DELETE http://localhost:4000/api/bookmarks/<BOOKMARK_ID> \
-H "Authorization: Bearer <TOKEN>"
— Список тегов с количеством
curl http://localhost:4000/api/tags \
-H "Authorization: Bearer <TOKEN>"
— Создать короткую ссылку
curl -X POST http://localhost:4000/api/shorten \
-H "Authorization: Bearer <TOKEN>" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com"}'
— С указанием истечения и привязкой к закладке
curl -X POST http://localhost:4000/api/shorten \
-H "Authorization: Bearer <TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"url":"https://example.com",
"expiresAt":"2025-12-31T23:59:59.000Z",
"bookmarkId":"<BOOKMARK_ID>"
}'
— Мои короткие ссылки
curl http://localhost:4000/api/urls \
-H "Authorization: Bearer <TOKEN>"
— Статистика по id
curl http://localhost:4000/api/urls/<SHORTLINK_ID>/stats \
-H "Authorization: Bearer <TOKEN>"
— Публичный редирект (без авторизации)
curl -i http://localhost:4000/api/s/<SHORT_CODE>
- Неверные данные запроса → 400
{ errors: [...] }(express‑validator) - Неавторизовано → 401
{ message: "Unauthorized" } - Не найдено → 404
{ message: "Not found" } - Истёкшая короткая ссылка → 410
Link expired - Прочие ошибки → 500
{ message, stack? }(в продеstackскрыт)
- Helmet включает стандартные security‑заголовки
- CORS:
CORS_ORIGINили*по умолчанию - Rate Limiting: окно
RATE_LIMIT_WINDOW_MS, максимумRATE_LIMIT_MAX
- В тестовом окружении (
NODE_ENV=test) сетевые запросы к внешним сайтам отключены и возвращаются пустые OG‑поля для стабильности тестов
npm run dev— запуск с watchnpm start— запуск прод‑сборки (NODE_ENV=production в Docker)npm test— тестыnpm run test:coverage— тесты с покрытиемnpm run lint— линтер ESLintnpm run format— форматирование Prettiernpm run docs— (сообщение) Swagger генерируется динамически на/api/docs
- Где открыть документацию? —
http://localhost:4000/api/docs - Нужен ли токен для редиректа коротких ссылок? — Нет, редирект публичный
- Можно ли повторно создать закладку на тот же URL? — Нет, в рамках одного пользователя уникальный индекс
(userId, url) - Как очистить БД в тестах? — Тесты сами очищают коллекции между сценариями
MIT — см. файл LICENSE.