{"id":19269235,"url":"https://github.com/gvozdenkov/impress-place-backend","last_synced_at":"2026-04-11T11:03:38.057Z","repository":{"id":214460134,"uuid":"735514665","full_name":"gvozdenkov/impress-place-backend","owner":"gvozdenkov","description":"Photo sharing service / backend","archived":false,"fork":false,"pushed_at":"2024-07-17T10:14:32.000Z","size":780,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-20T17:45:09.315Z","etag":null,"topics":["commitizen","commitlint","express","openapi","rest-api","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gvozdenkov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-12-25T07:43:55.000Z","updated_at":"2024-07-17T10:14:35.000Z","dependencies_parsed_at":"2025-01-05T12:41:57.263Z","dependency_job_id":"b6e3ef60-8076-46bd-a425-20b47624b931","html_url":"https://github.com/gvozdenkov/impress-place-backend","commit_stats":null,"previous_names":["gvozdenkov/mesto-project-plus","gvozdenkov/impress-place","gvozdenkov/impress-place-backend"],"tags_count":0,"template":false,"template_full_name":"yandex-praktikum/mesto-project-plus","purl":"pkg:github/gvozdenkov/impress-place-backend","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gvozdenkov%2Fimpress-place-backend","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gvozdenkov%2Fimpress-place-backend/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gvozdenkov%2Fimpress-place-backend/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gvozdenkov%2Fimpress-place-backend/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gvozdenkov","download_url":"https://codeload.github.com/gvozdenkov/impress-place-backend/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gvozdenkov%2Fimpress-place-backend/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31677819,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-11T08:18:19.405Z","status":"ssl_error","status_checked_at":"2026-04-11T08:17:08.892Z","response_time":54,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["commitizen","commitlint","express","openapi","rest-api","typescript"],"created_at":"2024-11-09T20:19:02.075Z","updated_at":"2026-04-11T11:03:38.030Z","avatar_url":"https://github.com/gvozdenkov.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Impress Place API\n\n\u003cbr /\u003e\n\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://gvozdenkov.github.io/algoschool/\"\u003e\n    \u003cimg src=\"README_static/logo.svg\" alt=\"Fibonacci Algorithmic School logo\" height=\"200\"\u003e\n  \u003c/a\u003e\n  \u003cbr/\u003e\u003cbr/\u003e\n  \u003cp align=\"center\"\u003eShare photos of amazing places with your friends\u003c/p\u003e\n  \u003cbr/\u003e\n\u003c/div\u003e\n\n## О проекте\n\nУчебный REST API бэкенд для сервиса размещения фотографий `Impress Place`. Реализовал регистрацию и\nаутентификацию пользователя с помощью `jwt` токенов. Пользователь может добавить фотографию, удалить\nсвою фотографию, поставить лайк любой фотографии и отредактировать свой профиль.\n\nПошёл дальше учебного задания и добавил документацию OpenAPI, докеризовал приложение и настроил\n`pre-commit` хуки `husky`\n\n- Бэкенд на `Express` / TypeScript\n- База данных `MongoDB`\n- ODM `Mongoose` для работы с базой\n\n![swagger docs](README_static/swagger.png)\n\n## Техники\n\n:boom: Применил подход `Contract first` в работе с API (вдохновило\n[видео Глеба Михеева](https://www.youtube.com/watch?v=-mzzT0b9K54)). Сначала создал спецификацию\n`OpenAPI` в онлайн редакторе [swagger editor](https://editor-next.swagger.io/). Файл спецификации\n[`openapi.yaml на гитхаб`](https://github.com/gvozdenkov/wish-magic/blob/main/backend/docs/openapi.yaml).\nПо нему создал REST API бэкенда.\n\nРазобрался, как автоматически локализовать `OpenAPI` спецификацию с помощью\n`swagger-i18n-extension`. При сборке контейнера из одного файла спецификации создаются\nлокализованные копии по языкам. После запуска в режиме разработки swagger спецификация доступна по\nадресу http://localhost:3000/api/v1/docs/en/ и http://localhost:3000/api/v1/docs/ru/\n\n:boom: Использовал версионирование API\n\n:boom: Организовал код по бизнес доменам, а не по техническим обязанностям. Например, в `/user`\nнаходятся все модули, относящиеся к `User` - контроллер, сервис, модель и утилиты. Таким образом\nсвязанные сущности находятся в одном месте и общая структура приложения выразительно говорит о то,\nпро что это приложение. Так легче ориентироваться в коде\n\n:boom: Покрыл API e2e тестами с помощью `Mocha` и `Supertest`. Использовал только e2e тесты\nэндпоинтов для тестирования внешних взаимодействий. Опирался на книгу Владимира Хорикова -\n[Принципы юнит-тестирования](https://www.piter.com/collection/bestsellery-manning/product/printsipy-yunit-testirovaniya).\nДля моего случая чистого REST API выбрал стиль тестирования Проверка выходных данных. Этот стиль\nчерезвычайно устойчив к рефакторингу котодов базы, потому что не завязан на внутреннюю реализацию. Я\nпроверяю только то, что система выдаёт внешнему клиенту. И сопровождение тестов простое.\n\n:boom: Для тестов в режиме разработки поднимается отдельный тестовый контейнер базы\n\n:boom: Настроил проверку перед коммитами с помощью `husky`, `lint-staged` и `commitlint`. Коммит не\nсоздаётся, если staged файлы не проходят линтер или сообщение коммита написано не по\n[`Conventional Commits`](https://www.conventionalcommits.org/en/v1.0.0/)\n\n## Local dev with Docker\n\n```bash\ngit clone git@github.com:gvozdenkov/impress-place-backend.git\n\ncd impress-place-backend\n\ncp .env.example .env\n\ndocker compose -f compose.dev.yaml up --build\n# or with Makefile\nmake run-dev\n\n# запуск e2e тестов\nyarn test\n```\n\nБэкенд стартует по адерсу `http:localhost:3000/api/v1`\n\nДокументация OpenAPI по адресу `http:localhost:3000/api/v1/docs/ru`\n\nДля запуска тестов `yarn test`. This will set `NODE_ENV` to `test` and run `mocha` integration tests\nin separate test container.\n\n## Планы по развитию\n\n- [ ] Перевезти проект бэкенда и фронтенда в монорепозиторий с и\n      [`pnpm workspace`](https://pnpm.io/workspaces) под управлением [`Nx`](https://nx.dev/)\n- [ ] Перевести бэкенда на [`Fastify`](https://fastify.dev/)\n- [ ] Поиграть с тем, чтобы написать бэк на чистой ноде, используя все возможностьи API ноды\n- [ ] Добавить загрузку картинок не по ссылке, а файлами\n- [ ] Прикрутить Redis для кэширования запросов к базе\n- [ ] Прикрутить авторизацию с помощью Goolge, Yandex и VK\n\n\u003cdetails\u003e\n\u003csummary\u003eWorkflow setup details\u003c/summary\u003e\n\n### Eslint\n\nInstall deps:\n\n```bash\nyarn add -D eslint eslint-config-airbnb-base eslint-config-airbnb-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-import-resolver-typescript eslint-plugin-import\n```\n\n### Prettier\n\nInstall exact versions (I have some bugs with prettier 3)\n\n```bash\nyarn add -D prettier@2.8.7 eslint-plugin-prettier@4.2.1 eslint-config-prettier\n```\n\nAdd to eslint config:\n\n```js\nmodule.exports = {\n  extends: [\n    // add at the end of array!\n    \"pretter\"\n  ],\n  plugins: {\n    \"prettier\"\n  },\n  rules: {\n    // highlight prettier errors\n    \"prettier/prettier\": [\"error\"],\n  }\n}\n```\n\nAdd script in `package.json` fix prettier styles:\n\n```json\n\"scripts\": {\n  \"prettier:write\": \"prettier --write ./**/*.{ts,js} ./*.{json,md,yml} -l\",\n}\n```\n\n### Commit check\n\nThis project is [Commitizen](https://www.npmjs.com/package/commitizen?activeTab=readme) friendly. So\nyou can easy create commits in a step by step guide by run:\n\n```bash\nyarn cz\n```\n\nIf you are mannually create commit message it will be linted with `commitlint` to lint commit\nmessages acording with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).\n\n- `husky` \u0026 `lint-stage` to fix \u0026 lint staged files before commit.\n- `commitlint` to lint commit message according\n  [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)\n\nInstall deps:\n\n```bash\nyarn add -D cz-git commitizen @commitlint/cli @commitlint/config-conventional @commitlint/format\n```\n\n## API Documentation\n\nI use the Contract First approach when developing APIs based on OpenApi v3.1\n\n1. Created a contract document describing the API in the `/docs/openapi.yaml` folder.\n2. I use the `swagger-i18n-extension` package to generate localized copies of the docs.\n\n```json\n \"swagger:docs\": \"yarn swagger-i18n-extension translate-all ./docs/openapi.yaml\"\n```\n\nThis script generate localized versions from the `./docs/openapi.yaml`\n\nI use `swagger-ui-express` serve docs on `/docs` route. For example for `en`: `/api/v1/docs/en`\n\n```ts\n// app.ts\n\n// add router for app \u0026 docs\napp.use('/api/v1', router);\n\n// router.ts\n\nrouter.use('/docs', docRouter);\n\n// docRouter.ts\n\n// download .yaml specs\nvar openApiSpecRu = YAML.load(fs.readFileSync('docs/openapi.rus.yaml', 'utf-8'));\nvar openApiSpecEn = YAML.load(fs.readFileSync('docs/openapi.eng.yaml', 'utf-8'));\n\n// server localized swagger docs on different routes\ndocRouter.use('/en', swaggerUi.serveFiles(openApiSpecEn, {}), swaggerUi.setup(openApiSpecEn));\ndocRouter.use('/ru', swaggerUi.serveFiles(openApiSpecRu, {}), swaggerUi.setup(openApiSpecRu));\n```\n\n## Test Driven Development\n\nI decided to use only integration tests because only the server responses are important to the end\nuser. The model and access to the database do not require testing. If something goes wrong at this\nstage, the tests will automatically fail. This makes the tests resistant to refactoring. You can\nreplace the database and internal processes, the only important thing is that the server response\nremains the same\n\n```bash\nyarn add mocha @types/mocha chai @types/chai tsx\n```\n\nUse Mocha \u0026 Chai for testing\n\nUse `tsx` to work with .ts test files. Create `mocharc.json` to config Mocha for TS (spec/test files\nlocated near the tested files in `src`):\n\n```json\n{\n  \"require\": [\"tsx\"],\n  \"extensions\": [\"ts\"],\n  \"spec\": [\"src/**/*.spec.*\"],\n  \"watch-files\": [\"src\"]\n}\n```\n\nAdd script to `package.json`:\n\n```diff\n  \"scripts\": {\n+    \"test\": \"cross-env NODE_ENV=test mocha 'src/**/*.{spec,test}.ts'\",\n+    \"test:watch\": \"mocha --watch\",\n  },\n```\n\nRun `yarn test` to run mocha tests\n\nRun `yarn test:watch` to run mocha in watch mode to rerun tests on edits (not workin well with ts\nnow...)\n\n\u003c/details\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgvozdenkov%2Fimpress-place-backend","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgvozdenkov%2Fimpress-place-backend","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgvozdenkov%2Fimpress-place-backend/lists"}