Я разбил его на этапы (эпики), внутри каждого — небольшие задачи. После каждого этапа проект уже остается рабочим.
Цель: создать фундамент проекта.
- создать репозиторий Git
- настроить
.gitignore - создать README
- выбрать лицензию (MIT)
- настроить Makefile
- docker-compose
- конфигурацию через
.env - базовую структуру проекта
Например
image-host/
cmd/
server/
internal/
config/
handler/
service/
repository/
storage/
model/
pkg/
web/
migrations/
deploy/
docker-compose.yml
Makefile
Цель:
Получить работающий сервер.
✔ запуск Gin
✔ health endpoint
GET /health
ответ
OK
сделать роутер
POST /upload
GET /image/:id
пока пусть возвращают
Not implemented
Что изучишь
- Gin
- middleware
- роутинг
Теперь сервер реально принимает изображения.
принимать
multipart/form-data
сохранять файл
пока
storage/
abc123.png
генерировать id
KSUID
возвращать
{
"id":"abc123",
"url":"http://localhost/image/abc123"
}
GET
/image/:id
отдает картинку.
После этого MVP уже работает.
Теперь перестаем надеяться на файловую систему.
Создаем таблицу
images
например
id
filename
mime_type
size
storage_key
created_at
expires_at
После загрузки
1 сохраняем файл
2 записываем метаданные
GET
ищет запись в Postgres.
Изучишь
- pgx
- миграции
- repository pattern
Самый простой.
Страница
+
Перетащи файл
или
Выбери файл
кнопка
Upload
после загрузки
Скопировать ссылку
Все.
Никаких React.
Обычный HTML.
Максимум Alpine.js.
Отдельный модуль.
Команды
/start
/help
если прислали фото
↓
бот скачивает файл
↓
загружает
↓
отвечает
Готово
https://...
После этого сервис уже реально можно использовать.
Теперь избавляемся от хранения файлов на диске.
Вместо
storage/
используем
MinIO
Меняется только Storage слой.
Все остальные слои ничего не замечают.
Это хороший показатель правильной архитектуры.
При загрузке
- проверить MIME
- проверить размер
если JPEG
↓
конвертация
↓
WebP
если слишком большое
↓
resize
↓
сохранить
Добавить
thumbnail
например
400px
Добавляем
expires_at
Можно выбрать
10 минут
1 час
1 день
7 дней
никогда
Фоновый worker
каждую минуту
ищет
expires_at < now()
↓
удаляет файл
↓
удаляет запись.
Добавить
Rate Limit
Максимальный размер файла
MIME Validation
Magic Bytes
CORS
Security Headers
Переходим на
log/slog
Добавляем
request id
время обработки
ip
user agent
Добавляем
/swagger
Автоматически
go test
go fmt
golangci-lint
docker build
при push.
Ubuntu
Docker Compose
Nginx
HTTPS
Домен
Красивый README
Архитектурная схема
Скриншоты
Диаграмма
Не раньше.
Добавить
- авторизацию
- личный кабинет
- папки
- drag&drop нескольких файлов
- API-ключи
- Prometheus
- Grafana
- OpenTelemetry
- S3 (AWS)
- Cloudflare R2
- удаление по секретной ссылке
- одноразовые ссылки
- ограничение количества просмотров
- фоновые очереди
Я бы хотел вести проект так же, как это делают в командах.
Для каждой задачи мы будем соблюдать одинаковый цикл:
- Обсуждаем, что реализуем и почему именно так.
- Проектируем интерфейсы и структуру каталогов.
- Ты реализуешь функциональность.
- Я делаю code review и предлагаю улучшения.
- Если нужно — рефакторим.
- Только после этого переходим к следующей задаче.
Такой подход научит тебя не только писать код, но и принимать архитектурные решения.
Я бы сразу ввел одно важное правило:
Не использовать ИИ для генерации больших кусков кода.
Лучше писать самостоятельно, а меня использовать как тимлида: обсудить архитектуру, проверить реализацию, найти баги, объяснить ошибки и предложить улучшения. Такой подход дает гораздо более прочные навыки и позволяет уверенно рассказывать о проекте на собеседовании.
Мне нравится эта идея, и я готов вести проект до полноценного релиза. В итоге у тебя получится не просто пет-проект, а сервис, который можно развернуть на VPS, пользоваться самому и показывать работодателям как пример качественной Go-разработки.