Акаунти та вхід
Реєстрація по email і телефону, підтвердження пошти, вхід через Google, GitHub і Facebook, скидання пароля, вихід з усіх пристроїв.
Готовий фундамент для SPA/PWA з акаунтами, real-time і AI. Документація з фокусом на бекенд.
vibe-template — це не порожній «скелет», а робочий застосунок, з якого ви починаєте свій продукт. Зазвичай перші тижні нового проєкту йдуть на одне й те саме: реєстрація, логін, скидання пароля, сесії, CSRF, завантаження файлів, листи, push, адмінка, деплой. У цьому шаблоні все це вже написано, покрито тестами та задокументовано. Ви клонуєте репозиторій, налаштовуєте .env і одразу пишете свою бізнес-логіку.
Шаблон складається з трьох пакетів в одному репозиторії (pnpm workspace):
| Пакет | Що це | Документація |
|---|---|---|
backend | API-сервер і WebSocket на TypeScript + uWebSockets.js | Огляд бекенду |
frontend | SPA/PWA на Vue 3 з Pinia, vue-router і vue-i18n | Фронтенд |
shared | Схеми, типи та enum-и, які однаково імпортують і фронтенд, і бекенд | Пакет shared |
Новачок у проєкті?
Почніть з Вступу, далі Швидкий старт — локальний запуск за 10 хвилин. Потім прочитайте Як влаштований бекенд: там простими словами пояснено, що відбувається з запитом і навіщо кожен шар.
shared: з неї бекенд перевіряє запит під час виконання, а тип виводиться (typeof Schema.infer) і імпортується і бекендом, і фронтендом. Структуру БД описано один раз Drizzle-схемою: з неї генеруються міграції й виводяться типи рядків (InferSelectModel). Змінили схему — pnpm typecheck показує всі місця на сервері й клієнті, які треба оновити. Як це влаштовано — Звідки беруться типи.validateRuntimeConfig). Виняток — CSRF: за замовчуванням він лише логує порушення, для продакшену треба увімкнути CSRF_ENFORCE=true..env./ready, graceful shutdown, Dockerfile з healthcheck, конфігурація через env. Як зібрати й запустити — Деплой.AGENTS.md з правилами роботи і feature_list.json зі списком фіч, тож Claude Code чи інший агент працює за тими ж правилами, що й люди.Три приклади з коду шаблону, що показують, що дають ці переваги, коли ви змінюєте продукт.
Список користувачів в адмінці описано однією схемою в shared (admin-user-controller.ts). Тип виводиться з неї, а не пишеться окремо:
export const AdminUserListItemSchema = type({
id: 'string',
name: 'string', // перейменуємо на fullName
email: 'string',
registeredAt: 'string',
})
export type AdminUserListItem = typeof AdminUserListItemSchema.inferПісля перейменування name → fullName TypeScript показує обидві сторони:
packages/backend typecheck: src/app/controllers/http/admin/admin-user-controller.ts(29,37):
error TS2322: Type 'AdminUserListItem[]' is not assignable to type
'{ id: string; fullName: string; email: string; registeredAt: string; }[]'.
src/views/admin/AdminUsersView.vue(165,37): error TS2339: Property 'name' does not exist on type
'{ id: string; fullName: string; email: string; registeredAt: string; }'.pnpm typecheck зупиняється на першому пакеті з помилкою, тому помилку фронтенду видно після виправлення бекенду або в pnpm --filter frontend typecheck.
Без спільного типу бекенд почав би віддавати нове поле, а таблиця в адмінці мовчки показувала б порожню колонку. Та сама схема описує відповідь маршруту GET /api/admin/users в API Playground, тож документація API теж не розійдеться з типом.
Обмеження
Захист працює лише там, де обидві сторони справді імпортують тип з shared. ResponseSchema у маршрутах служить для опису API і не перевіряє відповідь під час виконання: захист відповіді дає TypeScript під час компіляції. Новий код варто писати так, як у прикладі вище: схема в shared, тип через .infer, і цей тип у сервісі, контролері й компоненті.
Чат підтримки працює через два транспорти: повідомлення йдуть по WebSocket (ws/support-controller.ts), а історія чату завантажується по HTTP (http/support/support-controller.ts). Обидва контролери тонкі й викликають той самий supportService:
// WebSocket: нове повідомлення в чат
void supportService.chat(BigInt(userData.userId), payload.message, userData.uuid, context.signal)
// HTTP: історія чату
const result = await supportService.getChatHistory(userId)Правила чату (ліміти, збереження історії, звернення до AI) живуть в одному місці. Якщо їх змінити, зміниться поведінка обох транспортів.
Сервіс отримує дані тільки через репозиторій, тому в тесті репозиторій легко підмінити. Фрагмент з avatar-service.spec.ts:
const userRepositoryMock = { findById: vi.fn(), update: vi.fn() }
vi.mock('#app/repositories/index.js', () => ({
userRepository: userRepositoryMock,
}))
userRepositoryMock.findById.mockResolvedValue({ id: 7n, name: 'Alice', avatar: null })
const result = await avatarService.uploadAvatar(7n, file)
expect(Result.isOk(result)).toBe(true)Тест перевіряє логіку завантаження аватара за мілісекунди, без PostgreSQL, Redis і S3.
Нижче перелічено функціонал, яким можна користуватися одразу. Префікс API за замовчуванням — /api (appConfig.pathPrefix).
| Можливість | Як це працює | Детальніше |
|---|---|---|
| Реєстрація по email | POST /auth/register/email, лист з посиланням для підтвердження | Автентифікація |
| Підтвердження email | POST /auth/verify-email, повторна відправка resend-verification-email | Пошта |
| Реєстрація і вхід по телефону | SMS-код: register/phone/start → confirm → complete-profile | Автентифікація |
| Вхід через соцмережі (OAuth) | Google, GitHub, Facebook: auth/oauth/:provider/redirect і /callback | Автентифікація |
| Логін / логаут | POST /auth/login, /logout, /logout-all (вийти на всіх пристроях) | Автентифікація |
| Зміна і скидання пароля | change-password, forgot-password → лист → reset-password; скидання через SMS | Автентифікація |
| Прив'язка телефону / email | link-phone/start, link-phone/confirm, link-email/start | Автентифікація |
| Аватар користувача | POST /avatar/upload (S3/MinIO), DELETE /avatar | Сховище файлів |
| Ролі | Звичайний користувач і адміністратор, guard-и auth_guard і admin_guard | Ролі та можливості |
| Можливість | Що дає | Детальніше |
|---|---|---|
| Сесії в Redis | HttpOnly-cookie з HMAC-підписом, ротація токена, миттєве відкликання сесії | Сесії |
| CSRF-захист | Токен для всіх запитів, що змінюють дані | CSRF |
| CORS | Whitelist дозволених доменів | CORS |
| CSP та security-заголовки | Content-Security-Policy, X-Frame-Options та інші заголовки на кожну відповідь | Заголовки |
| Rate limiting | Глобальні та помаршрутні ліміти, в пам'яті або спільні через Redis | Ліміти |
| Захист від перебору | Throttling спроб входу, OTP-кодів і скидання пароля | Політика входу |
| Trust proxy | Коректний IP клієнта за nginx / балансувальником | Trust proxy |
| Валідація вхідних даних | Кожен маршрут описує схему arktype; некоректний запит не дійде до вашого коду | Валідація |
GET /main/update-ws-token).broadcast-service).Детальніше: WebSocket.
send-test-email для перевірки налаштувань./push-subscriptions/*).Детальніше: Пошта і Web Push сповіщення.
Детальніше: Сховище файлів (S3).
AI_TEXT_DRIVER або назвою моделі в промпті.Детальніше: AI-інтеграції.
Доступ лише для ролі адміністратора — Ролі та можливості.
Детальніше: Фронтенд.
| Можливість | Що є | Детальніше |
|---|---|---|
| База даних | PostgreSQL + Drizzle ORM, міграції, seed, скрипт створення БД | База даних |
| Конфігурація | Усе через змінні оточення, перевірка конфігурації при старті | Конфігурація |
| Логи та помилки | Pino (структуровані логи), Sentry на бекенді й фронтенді | Observability |
| Health-check | GET /ping (процес живий), GET /ready (БД і Redis доступні) | Деплой |
| Кешування | Роздача статики з ETag і Cache-Control, процесні кеші | Кешування та ETag |
| Тести | Vitest для бекенду, тест, що стежить за правилами шарів архітектури | Тестування |
| API Playground | Окремий dev-застосунок для ручного виклику HTTP і WS-маршрутів без Postman | API Playground |
| Деплой | Збірка в dist, graceful shutdown, робота за проксі, роздача фронтенду з того ж процесу | Деплой |
Шаблон розробив Олександр Блажейко (GitHub, blazheiko@gmail.com). Детальніше — на сторінці Про автора.