Skip to content

vibe-templateFull-stack TypeScript шаблон

Готовий фундамент для SPA/PWA з акаунтами, real-time і AI. Документація з фокусом на бекенд.

vibe-template

Що таке цей шаблон ​

vibe-template — це не порожній «скелет», а робочий застосунок, з якого ви починаєте свій продукт. Зазвичай перші тижні нового проєкту йдуть на одне й те саме: реєстрація, логін, скидання пароля, сесії, CSRF, завантаження файлів, листи, push, адмінка, деплой. У цьому шаблоні все це вже написано, покрито тестами та задокументовано. Ви клонуєте репозиторій, налаштовуєте .env і одразу пишете свою бізнес-логіку.

Шаблон складається з трьох пакетів в одному репозиторії (pnpm workspace):

ПакетЩо цеДокументація
backendAPI-сервер і WebSocket на TypeScript + uWebSockets.jsОгляд бекенду
frontendSPA/PWA на Vue 3 з Pinia, vue-router і vue-i18nФронтенд
sharedСхеми, типи та enum-и, які однаково імпортують і фронтенд, і бекендПакет shared

Новачок у проєкті?

Почніть з Вступу, далі Швидкий старт — локальний запуск за 10 хвилин. Потім прочитайте Як влаштований бекенд: там простими словами пояснено, що відбувається з запитом і навіщо кожен шар.

Чому саме цей шаблон ​

  • Не треба писати фундамент. Автентифікація, безпека, пошта, файли, push, адмінка й AI вже працюють. Це тисячі рядків коду, які ви не пишете й не дебажите.
  • Продуктивність. uWebSockets.js — один з найшвидших HTTP/WebSocket-серверів для Node.js за публічними бенчмарками (власних вимірювань для шаблону немає). Real-time не потребує окремого сервісу.
  • Єдине джерело істини замість дубльованих типів. Контракт API описано один раз arktype-схемою в пакеті shared: з неї бекенд перевіряє запит під час виконання, а тип виводиться (typeof Schema.infer) і імпортується і бекендом, і фронтендом. Структуру БД описано один раз Drizzle-схемою: з неї генеруються міграції й виводяться типи рядків (InferSelectModel). Змінили схему — pnpm typecheck показує всі місця на сервері й клієнті, які треба оновити. Як це влаштовано — Звідки беруться типи.
  • Передбачувана структура. Кожна фіча складається з однакових шарів. Знаючи одну, ви орієнтуєтесь у всіх. Правила шарів перевіряються тестом, тому архітектура не «розпливається» з часом.
  • Безпечні значення за замовчуванням. Більшість захистів (CSP, security-заголовки, rate limiting, HttpOnly-cookie) увімкнені одразу, а сервер не стартує з частиною небезпечних налаштувань (перевірка validateRuntimeConfig). Виняток — CSRF: за замовчуванням він лише логує порушення, для продакшену треба увімкнути CSRF_ENFORCE=true.
  • PWA і push одразу. Застосунок можна встановити на пристрій з меню, а сервер уміє надсилати Web Push навіть коли вкладку закрито. Для push треба лише VAPID-ключі в .env.
  • Готово до продакшену. Логи Pino, Sentry, health-check /ready, graceful shutdown, Dockerfile з healthcheck, конфігурація через env. Як зібрати й запустити — Деплой.
  • Тести з коробки. Понад тисячу unit- та інтеграційних тестів на Vitest. Тести з PostgreSQL створюють для себе окрему тимчасову базу, а Playwright перевіряє фронтенд у браузері. Детальніше — Тестування.
  • Приклади юридичних сторінок. Privacy Policy, Terms, Cookies Policy і AI Policy вже мають маршрути й сторінки у фронтенді. Це приклад-заготовка, а не готовий юридичний текст: замініть зміст на свій і погодьте з юристом перед запуском.
  • Зручно для роботи з AI-агентами. У репозиторії є AGENTS.md з правилами роботи і feature_list.json зі списком фіч, тож Claude Code чи інший агент працює за тими ж правилами, що й люди.

Як це виглядає на практиці ​

Три приклади з коду шаблону, що показують, що дають ці переваги, коли ви змінюєте продукт.

Змінили контракт — TypeScript показав, що зламалось ​

Список користувачів в адмінці описано однією схемою в shared (admin-user-controller.ts). Тип виводиться з неї, а не пишеться окремо:

ts
export const AdminUserListItemSchema = type({
    id: 'string',
    name: 'string', // перейменуємо на fullName
    email: 'string',
    registeredAt: 'string',
})
export type AdminUserListItem = typeof AdminUserListItemSchema.infer

Після перейменування name → fullName TypeScript показує обидві сторони:

text
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, і цей тип у сервісі, контролері й компоненті.

Один сервіс для HTTP і WebSocket ​

Чат підтримки працює через два транспорти: повідомлення йдуть по WebSocket (ws/support-controller.ts), а історія чату завантажується по HTTP (http/support/support-controller.ts). Обидва контролери тонкі й викликають той самий supportService:

ts
// 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:

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).

Автентифікація та акаунт ​

МожливістьЯк це працюєДетальніше
Реєстрація по emailPOST /auth/register/email, лист з посиланням для підтвердженняАвтентифікація
Підтвердження emailPOST /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Автентифікація
Прив'язка телефону / emaillink-phone/start, link-phone/confirm, link-email/startАвтентифікація
Аватар користувачаPOST /avatar/upload (S3/MinIO), DELETE /avatarСховище файлів
РоліЗвичайний користувач і адміністратор, guard-и auth_guard і admin_guardРолі та можливості

Безпека ​

МожливістьЩо даєДетальніше
Сесії в RedisHttpOnly-cookie з HMAC-підписом, ротація токена, миттєве відкликання сесіїСесії
CSRF-захистТокен для всіх запитів, що змінюють даніCSRF
CORSWhitelist дозволених доменівCORS
CSP та security-заголовкиContent-Security-Policy, X-Frame-Options та інші заголовки на кожну відповідьЗаголовки
Rate limitingГлобальні та помаршрутні ліміти, в пам'яті або спільні через RedisЛіміти
Захист від переборуThrottling спроб входу, OTP-кодів і скидання пароляПолітика входу
Trust proxyКоректний IP клієнта за nginx / балансувальникомTrust proxy
Валідація вхідних данихКожен маршрут описує схему arktype; некоректний запит не дійде до вашого кодуВалідація

Real-time (WebSocket) ​

  • Одне WebSocket-з'єднання на клієнта, авторизоване коротким токеном (GET /main/update-ws-token).
  • Власний протокол повідомлень і диспетчер WS-маршрутів, які описуються так само, як HTTP-маршрути.
  • Відстеження, хто онлайн, та історія онлайн-активності (для адмінки).
  • Розсилка подій від сервера конкретному користувачу або каналу (broadcast-service).
  • Heartbeat і таймаути, щоб «мертві» з'єднання закривались самі.

Детальніше: WebSocket.

Сповіщення та пошта ​

  • Транзакційні листи через SMTP (nodemailer): підтвердження email, скидання пароля. Є скрипт send-test-email для перевірки налаштувань.
  • Web Push (VAPID): підписка, оновлення, деактивація, тестова відправка, журнал доставок і статистика по кожній підписці (/push-subscriptions/*).

Детальніше: Пошта і Web Push сповіщення.

Файли ​

  • Завантаження в S3-сумісне сховище (AWS S3, MinIO та ін.), presigned-посилання.
  • Готовий приклад використання — аватари та скриншоти статей бази знань.

Детальніше: Сховище файлів (S3).

AI ​

  • Чат підтримки (Support AI): відповідає на питання користувача на основі бази знань. Пошук релевантних статей через embeddings у PostgreSQL (pgvector), потокова відповідь через WebSocket, історія чату.
  • База знань: CRUD статей, скриншоти, переіндексація однієї статті або всіх, початкове наповнення.
  • Промпти в БД: системні промпти редагуються з адмінки без деплою, їх можна протестувати перед збереженням.
  • Облік токенів: статистика використання AI.
  • Кілька провайдерів: OpenAI, xAI (Grok), Mistral. Провайдер обирається змінною AI_TEXT_DRIVER або назвою моделі в промпті.

Детальніше: AI-інтеграції.

Адмін-панель ​

  • Список користувачів.
  • Хто зараз онлайн (оновлюється в реальному часі через WebSocket) та історія онлайну.
  • Керування базою знань і AI-промптами, статистика використання AI.

Доступ лише для ролі адміністратора — Ролі та можливості.

Фронтенд ​

  • Vue 3 + Pinia + vue-router, guard-и маршрутів для авторизованих і адмін-сторінок.
  • Готові сторінки: логін/реєстрація, підтвердження email, скидання пароля, акаунт, чат підтримки, адмінка, юридичні сторінки (Privacy, Terms, Cookies, AI Policy).
  • 18 мов інтерфейсу (vue-i18n), зокрема українська та англійська.
  • PWA: встановлення на пристрій, офлайн-режим, push-сповіщення.
  • HTTP-клієнт, який сам додає CSRF-токен і обробляє сесію; WebSocket-клієнт з перепідключенням.
  • Unit-тести (Vitest) та e2e (Playwright).

Детальніше: Фронтенд.

Інфраструктура та якість ​

МожливістьЩо єДетальніше
База данихPostgreSQL + Drizzle ORM, міграції, seed, скрипт створення БДБаза даних
КонфігураціяУсе через змінні оточення, перевірка конфігурації при стартіКонфігурація
Логи та помилкиPino (структуровані логи), Sentry на бекенді й фронтендіObservability
Health-checkGET /ping (процес живий), GET /ready (БД і Redis доступні)Деплой
КешуванняРоздача статики з ETag і Cache-Control, процесні кешіКешування та ETag
ТестиVitest для бекенду, тест, що стежить за правилами шарів архітектуриТестування
API PlaygroundОкремий dev-застосунок для ручного виклику HTTP і WS-маршрутів без PostmanAPI Playground
ДеплойЗбірка в dist, graceful shutdown, робота за проксі, роздача фронтенду з того ж процесуДеплой

Автор ​

Шаблон розробив Олександр Блажейко (GitHub, blazheiko@gmail.com). Детальніше — на сторінці Про автора.

З чого почати ​

  1. Вступ — для чого шаблон і кому він підходить.
  2. Швидкий старт — встановлення та перший запуск.
  3. Як влаштований бекенд — пояснення для початківців.
  4. Структура проєкту — де що лежить.
  5. Як додати фічу — покроковий туторіал: від таблиці в БД до маршруту.

Автор: Олександр Блажейко · blazheiko@gmail.com