Современный full-stack сервис для создания и проведения опросов с множественным выбором
Легкий, быстрый и масштабируемый микросервис для создания временных опросов с автоматическим истечением срока действия. Построен на Go + Redis в backend и Next.js 15 + React 19 на frontend.
Surway — это full-stack приложение для создания и проведения опросов (surveys/polls) с поддержкой множественного выбора вариантов. Проект использует Redis в качестве основного хранилища данных с нативной поддержкой TTL (Time To Live), что обеспечивает автоматическое удаление устаревших опросов и высокую производительность.
- Множественный выбор — пользователи могут выбрать несколько вариантов ответа
- Временные опросы — автоматическое удаление по истечении TTL (от 7 до 30 дней)
- Real-time результаты — мгновенное отображение результатов голосования
- REST API с Swagger документацией
- Атомарные операции — безопасный подсчет голосов через Redis pipeline
- Graceful shutdown — корректное завершение работы сервера
- Production-ready — Docker Compose + Caddy для SSL и reverse proxy
- Современный UI — анимации, графики, адаптивный дизайн
Проект следует принципам Clean Architecture и разделен на независимые слои:
surway/
├── backend/ # Go backend
│ ├── cmd/
│ │ └── api/
│ │ └── main.go # Точка входа с Swagger аннотациями
│ ├── internal/
│ │ ├── handler/ # HTTP handlers (Gin framework)
│ │ │ ├── poll.go # CRUD операции для опросов
│ │ │ └── router.go # Роутинг + middleware (CORS, logging)
│ │ ├── service/ # Бизнес-логика
│ │ │ ├── poll.go
│ │ │ └── poll_test.go # Unit тесты
│ │ ├── storage/ # Слой данных
│ │ │ └── redis.go # Redis реализация Storage интерфейса
│ │ ├── model/ # Модели данных
│ │ │ └── poll.go # Poll, Request/Response структуры
│ │ ├── config/ # Конфигурация
│ │ │ └── config.go # Загрузка из env переменных
│ │ └── lib/
│ │ └── random/ # Генерация коротких ID для опросов
│ │ └── random.go
│ ├── docs/ # Swagger документация (auto-generated)
│ │ ├── docs.go
│ │ └── swagger.yaml
│ ├── Dockerfile # Multi-stage build
│ ├── go.mod
│ └── go.sum
│
├── frontend/ # Next.js 15 frontend
│ ├── app/ # App Router (Next.js 15)
│ │ ├── page.tsx # Главная страница
│ │ ├── layout.tsx # Root layout
│ │ ├── create/ # Создание опроса
│ │ ├── [id]/ # Страницы опроса (динамический роутинг)
│ │ │ ├── page.tsx # Голосование
│ │ │ └── results/ # Результаты
│ │ ├── config/ # Конфигурация приложения
│ │ └── services/ # API клиент
│ ├── components/ # React компоненты
│ │ ├── ui/ # UI компоненты
│ │ └── analytics/ # Графики и визуализация
│ ├── public/ # Статические файлы
│ ├── Dockerfile # Multi-stage build для production
│ ├── package.json
│ └── next.config.js
│
├── configs/ # Конфигурационные файлы
│ ├── redis.conf # Настройки Redis для production
│ └── README.md
│
├── docker-compose.yml # Development environment
├── docker-compose.prod.yml # Production environment с Caddy
├── Caddyfile # Reverse proxy + автоматический SSL
├── Makefile # Команды для разработки
└── .env # Переменные окружения (не коммитится)
- Handler Layer — обработка HTTP запросов, валидация, маппинг ошибок
- Service Layer — бизнес-логика, генерация ID, создание URL
- Storage Layer — работа с Redis через интерфейс
- Middleware — CORS, структурированное логирование (slog), recovery
- Swagger — автогенерация OpenAPI документации
- Server Components — для SEO и производительности
- Client Components — для интерактивности
- API Service — централизованный клиент для работы с backend
- Recharts — визуализация результатов в виде графиков
- Framer Motion — плавные анимации и переходы
- Tailwind CSS 4 — современный стилинг
Использует два типа структур данных:
- String для метаданных опроса (JSON с TTL)
- Hash для счетчиков голосов (атомарный HINCRBY)
- Pipeline для атомарности создания/голосования
| Метод | Путь | Описание |
|---|---|---|
POST |
/api/v1/polls |
Создать новый опрос |
POST |
/api/v1/polls/{id}/vote |
Проголосовать (множественный выбор) |
GET |
/api/v1/polls/{id}/results |
Получить результаты опроса |
GET |
/health |
Health check endpoint |
GET |
/swagger/* |
Swagger UI документация |
Создание опроса:
curl -X POST http://localhost:8080/api/v1/polls \
-H "Content-Type: application/json" \
-d '{
"title": "Какие языки программирования вы используете?",
"options": ["Go", "Python", "JavaScript", "Rust", "TypeScript"]
}'Ответ:
{
"poll_id": "abc123",
"vote_url": "http://localhost:8080/api/v1/polls/abc123/vote",
"results_url": "http://localhost:8080/api/v1/polls/abc123/results"
}Голосование (множественный выбор):
curl -X POST http://localhost:8080/api/v1/polls/abc123/vote \
-H "Content-Type: application/json" \
-d '{
"option_indices": [0, 2, 4]
}'Получение результатов:
curl http://localhost:8080/api/v1/polls/abc123/results- Docker 20.10+ и Docker Compose v2
- Или локально:
- Go 1.24.2+
- Node.js 20+
- Redis 7+
-
Клонируйте репозиторий:
git clone https://github.com/AlexeyLars/surway.git cd surway -
Запустите все сервисы:
make docker-up # или docker compose up -d -
Доступ к приложению:
- Frontend: http://localhost:3000
- Backend API: http://localhost:8080
- Swagger UI: http://localhost:8080/swagger/index.html
- Health check: http://localhost:8080/health
-
Просмотр логов:
make docker-logs # или docker compose logs -f -
Остановка:
make docker-down # или docker compose down
-
Настройте переменные окружения:
cd backend cp .env.example .env # если есть, или создайте .env
-
Установите зависимости:
make deps # или go mod download -
Запустите Redis:
docker run -d -p 6379:6379 redis:7-alpine
-
Запустите backend:
make run # или go run cmd/api/main.go -
Сгенерируйте Swagger документацию:
go install github.com/swaggo/swag/cmd/swag@latest swag init -g cmd/api/main.go
-
Настройте переменные окружения:
cd frontend cp env.example .env.local -
Установите зависимости:
npm install
-
Запустите dev сервер:
npm run dev
| Переменная | Описание | Значение по умолчанию |
|---|---|---|
ENV |
Окружение (dev/prod) | dev |
SERVER_HOST |
Хост сервера | 0.0.0.0 |
SERVER_PORT |
Порт сервера | 8080 |
SERVER_READ_TIMEOUT |
Таймаут чтения | 10s |
SERVER_WRITE_TIMEOUT |
Таймаут записи | 10s |
SERVER_SHUTDOWN_TIMEOUT |
Graceful shutdown таймаут | 5s |
BASE_URL |
Базовый URL для генерации ссылок | http://localhost:8080 |
| Переменная | Описание | Значение по умолчанию |
|---|---|---|
REDIS_HOST |
Хост Redis | localhost |
REDIS_PORT |
Порт Redis | 6379 |
REDIS_PASSWORD |
Пароль Redis | (пусто) |
REDIS_DB |
Номер БД Redis | 0 |
| Переменная | Описание | Значение по умолчанию |
|---|---|---|
POLL_DEFAULT_TTL |
TTL опроса по умолчанию | 168h (7 дней) |
POLL_MAX_TTL |
Максимальный TTL | 720h (30 дней) |
Client-side (NEXT_PUBLIC_*):
NEXT_PUBLIC_API_PROTOCOL— http/httpsNEXT_PUBLIC_API_HOST— localhostNEXT_PUBLIC_API_PORT— 8080NEXT_PUBLIC_API_VERSION— v1NEXT_PUBLIC_FRONTEND_PROTOCOL— httpNEXT_PUBLIC_FRONTEND_HOST— localhostNEXT_PUBLIC_FRONTEND_PORT— 3000
Server-side (для SSR):
API_INTERNAL_PROTOCOL— httpAPI_INTERNAL_HOST— backendAPI_INTERNAL_PORT— 8080
# Environment
ENV=dev
# Server
SERVER_HOST=0.0.0.0
SERVER_PORT=8080
BASE_URL=http://localhost:8080
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
# Polls
POLL_DEFAULT_TTL=168h
POLL_MAX_TTL=720hmake help # Показать все доступные команды
make build # Собрать backend бинарник
make run # Запустить backend локально
make test # Запустить тесты с race detector
make test-coverage # Показать покрытие тестами
make docker-up # Запустить Docker Compose
make docker-down # Остановить Docker Compose
make docker-logs # Показать логи контейнеров
make docker-build # Собрать Docker образы
make fmt # Форматировать Go код
make lint # Запустить линтер
make deps # Установить/обновить зависимости
make clean # Удалить артефакты сборки# Backend
cd backend
go test -v ./... # Все тесты
go test -v -race ./... # С race detector
go test -cover ./... # С покрытием
go test -coverprofile=coverage.out ./... # Сохранить отчет
go tool cover -html=coverage.out # Открыть HTML отчет
# Frontend
cd frontend
npm run test # Jest тесты (если настроены)
npm run lint # ESLint проверкаBackend:
# Установка golangci-lint
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
# Запуск
make lint
# или
cd backend && golangci-lint runFrontend:
cd frontend
npm run lintSwagger документация генерируется автоматически из аннотаций в коде:
cd backend
swag init -g cmd/api/main.goДоступна по адресу: http://localhost:8080/swagger/index.html
Включает:
redis— Redis 7 с persistencebackend— Go API серверfrontend— Next.js приложение
Порты наружу:
- 6379 (Redis)
- 8080 (Backend)
- 3000 (Frontend)
Включает дополнительно:
caddy— Reverse proxy + автоматический SSL от Let's Encrypt
Особенности production конфига:
- Порты backend/frontend не выставлены наружу (только через Caddy)
- Redis с кастомной конфигурацией (
configs/redis.conf) - Автоматический SSL через Caddy
- Restart policy:
always
Настройка для production:
-
Отредактируйте
Caddyfile:your-domain.com { reverse_proxy frontend:3000 handle /api/* { reverse_proxy backend:8080 } } -
Обновите переменные в
docker-compose.prod.yml:BASE_URL=https://your-domain.com/api NEXT_PUBLIC_API_HOST=your-domain.com
-
Запустите:
docker compose -f docker-compose.prod.yml up -d
Метаданные опроса (String с TTL):
Ключ: poll:{poll_id}:info
Значение: JSON с Poll структурой
TTL: 168h (по умолчанию)
Счетчики голосов (Hash с TTL):
Ключ: poll:{poll_id}:votes
Значение: Hash {
"0": "15", # индекс опции -> количество голосов
"1": "42",
"2": "8",
...
}
TTL: 168h (синхронизирован с info)
Пример:
# Получить метаданные
redis-cli GET "poll:abc123:info"
# Получить голоса
redis-cli HGETALL "poll:abc123:votes"
# TTL
redis-cli TTL "poll:abc123:info"- Go 1.24.2 — основной язык
- Gin — web framework
- go-redis/v9 — Redis клиент
- cleanenv — загрузка конфигурации
- swaggo/swag — генерация Swagger документации
- slog — структурированное логирование (стандартная библиотека)
- testify — тестирование
- Next.js 15.5.4 — React framework с App Router
- React 19.1.0 — UI библиотека
- TypeScript 5 — типизация
- Tailwind CSS 4 — утилитарный CSS
- Recharts 3 — графики и визуализация
- Framer Motion 12 — анимации
- lucide-react — иконки
- Redis 7 — основное хранилище данных
- Docker & Docker Compose — контейнеризация
- Caddy 2 — reverse proxy + SSL
- Alpine Linux — базовые образы
- Защита от повторного голосования (cookies/IP)
- Rate limiting middleware
- WebSocket для live обновления результатов
- Темная тема UI
- Экспорт результатов (CSV, PDF)
- Аутентификация и личные кабинеты
- История созданных опросов
- Кастомизация опросов (цвета, фон)
- Аналитика и статистика
- CI/CD pipeline (GitHub Actions)
- Метрики и мониторинг (Prometheus + Grafana)
- E2E тесты (Playwright)
- Интеграционные тесты для API
Приветствуются любые предложения и улучшения!
- Fork репозитория
- Создайте feature-ветку от
develop:git checkout develop git checkout -b feature/amazing-feature
- Сделайте изменения и commit:
git commit -m 'feat: add amazing feature' - Push в вашу ветку:
git push origin feature/amazing-feature
- Откройте Pull Request в
developветку
- Backend: следуйте Effective Go и запускайте
make lint - Frontend: используйте ESLint конфигурацию проекта
- Commits: используйте Conventional Commits
feat:— новая функциональностьfix:— исправление багаdocs:— документацияrefactor:— рефакторингtest:— добавление тестовchore:— рутинные задачи
Этот проект создан в образовательных целях.
Alexey Lars
- GitHub: @AlexeyLars
- Backend README (если будет создан)
- Frontend README
- Configs README
- Swagger UI (при запущенном сервере)
Статус проекта: 🚀 Active Development
Создано с использованием Go, Next.js и Redis