Архітектура бекенду
Бекенд (packages/backend) — це TypeScript-сервер на uWebSockets.js без стороннього веб-фреймворку. Код розділений на дві частини: src/vendor/ — власне «ядро фреймворку» (HTTP/WS-сервер, роутер, читання тіла запиту, rate limit, сесії, логер), і src/app/ — прикладний код (маршрути, контролери, сервіси, репозиторії, трансформери, middleware). Контракти між бекендом і фронтендом (схеми валідації, типи відповідей, enum-и) живуть у workspace-пакеті shared.
Розподіл vendor/ та app/
| Каталог | Призначення | Приклади |
|---|---|---|
vendor/start/ | Запуск uWS-сервера, реєстрація маршрутів | server.ts, router.ts |
vendor/utils/network/ | Читання заголовків/тіла, WS-обробники | http-request-handlers.ts, ws-handlers.ts |
vendor/utils/routing/ | Диспетчер WS-подій | ws-api-dispatcher.ts |
vendor/utils/middlewares/ | Виконання ланцюжка middleware, базові guard-и | execute-httpMiddlewares.ts, auth-guard.ts |
vendor/utils/validation/ | Валідація payload і query маршруту, getTypedPayload | validation/ |
vendor/utils/rate-limit/ | HTTP-, WS- і upgrade-ліміти (у пам'яті та Redis) | rate-limit/ |
vendor/utils/session/ | Сесії в Redis, підпис cookie, CSRF-токен | session/ |
vendor/utils/storage/ | Файлове сховище (S3) | storage/ |
vendor/utils/ (інше) | context, env, helpers, tooling (серіалізація схем для doc), логер | utils/ |
vendor/contracts/, vendor/types/, vendor/constants/ | Контракт Validator<T> (ADR 0001), спільні типи ядра, константи | contracts/, types/ |
app/routes/ | Декларації HTTP- і WS-маршрутів | http-routes.ts, ws-routes.ts |
app/middlewares/ | Реєстр middleware за іменем | kernel.ts |
app/controllers/ | HTTP- та WS-контролери | main-controller.ts |
app/services/ | Бізнес-логіка, повертає Result з better-result | main-service.ts |
app/repositories/ | Доступ до БД через Drizzle | user-repository.ts |
app/transformers/ | Серіалізація рядків БД у публічні DTO | user-transformer.ts |
app/state/ | In-memory стан процесу (зокрема listenSocket) | state.ts |
app/websocket/ | Прикладна логіка WS: автентифікація, реєстр з'єднань, presence | ws-coordinator.ts, ws-session-auth.ts |
app/start/ | Ініціалізація при старті: реєстрація WS-хуків і валідатора | index.ts |
Ядро не імпортує прикладну бізнес-логіку напряму для WS: app/start/index.ts реєструє хуки через registerWsAppHooks і registerWsMessageValidator з ws-handlers.ts. Для HTTP ядро звертається до app/middlewares/kernel.ts (реєстр middleware за рядковим ім'ям) і до валідаторів з app/validate/.
INFO
Окремого каталогу events/ у packages/backend/src/app немає. Реалтайм-події реалізовано через WebSocket-маршрути (ws-routes.ts) і broadcastToChannel у server.ts.
Шари
- Route — об'єкт
defineRoute({ url, method, handler, validator, ResponseSchema, middlewares, rateLimit })(define-route.ts). Групи задаютьprefix, спільніmiddlewaresіrateLimit;routesHandlerу router.ts розгортає їх у плоский список і додаєappConfig.pathPrefix(за замовчуваннямapi, зміннаAPI_PATH_PREFIX). - Middleware — функція
(context, next); якщо вона не викликалаnext(), обробник не запускається (див. Middleware). - Controller — отримує
HttpContext, викликає сервіс, мапить помилкуResultу HTTP-відповідь черезmapControllerError. - Service — бізнес-логіка, повертає
Result.ok(...)/Result.err(...). - Repository — запити Drizzle (див. /backend/database).
- Transformer — прибирає чутливі поля (
password,sessionToken) і нормалізує дати.
Правила імпорту між шарами перевіряє layering.spec.ts (статичний аналіз імпортів, без завантаження модулів):
- контролери, middleware (зокрема
admin_guard) іapp/websocketне імпортують репозиторії, навіть як типи: дані — через сервіси (adminUserService,userRoleServiceтощо), типи — з сервісу абоshared; - поза
app/repositoriesрепозиторії імпортуються лише через barrel#app/repositories/index.js, і barrel експортує кожен модуль*-repository.ts; - репозиторії не логують: рішення «це варто залогувати» приймає сервіс;
- сервіси не імпортують
#database/db.jsі#database/schema.js— лише репозиторії.
Контракти в packages/shared
Пакет shared (index.ts) експортує schemas (вхідні схеми, напр. VerifyEmailInputSchema), responses (схеми відповідей, напр. InitResponseSchema), enums, errors, brands, utils. Маршрути імпортують їх як shared/schemas і shared/responses, а контролери — типи відповідей (InitResponse, PingResponse) з shared.
Наскрізний приклад: GET /api/main/init
- Маршрут. У http-routes.ts група з
prefix: "main"іmiddlewares: ["session_web", "csrf_guard"]містить:
defineRoute({
url: "/init",
method: "get",
handler: MainController.init.bind(MainController),
ResponseSchema: ResponseSchemas.InitResponseSchema,
description: "Initialize app state",
middlewares: ["auth_guard"],
}),Після routesHandler URL стає api/main/init, а ланцюжок middleware — session_web → csrf_guard → auth_guard.
- Middleware.
executeMiddlewaresбере функції з kernel.ts.auth_guard(auth-guard.ts) за відсутності автентифікації ставитьresponseData.status = 401і не викликаєnext(). - Controller.
MainController.init(main-controller.ts) викликаєmainService.init(session.sessionInfo ?? null); при помилці —mapControllerError, інакше повертає{ status: "ok", ...result.value }. - Service.
mainService.init(main-service.ts) перевіряє сесію, завантажує користувача черезuserRepository.findById(BigInt(userId)), генерує WS-токен (generateWsToken) і повертає CSRF-токен, дані користувача,wsUrl,wsTokenі конфіг сховища. - Repository.
userRepository.findById(user-repository.ts) читає рядокusers. Цей ендпоінт формує DTO користувача вручну в сервісі; трансформерuserTransformer.serializeвикористовується, наприклад, в auth-service.ts (див. Репозиторії). - Відповідь. Повернуте значення handler-а записується в
responseData.payloadі серіалізується в JSON уsendResponse(server.ts). Деталі — на сторінці «Життєвий цикл запиту».
Найпростіший маршрут — GET /api/ping (MainController.ping → mainService.ping() → { status: "ok" }) — проходить той самий шлях, але без middleware і БД. Як додати власний ресурс за цими шарами — див. /backend/adding-a-feature.