Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Go-server (HTTP-сервер на Go)

Мини-проект, чтобы руками понять клиент-серверную архитектуру и REST на чистой стандартной библиотеке Go.

Что уже есть

  • GET / — проверка, что сервер жив: возвращает Hello, Go server!.

  • POST /note — принимает JSON заметки и возвращает её же.

    Успех: 201 Created .

    Ошибки:

    • 400 Bad Request — невалидный JSON ({"detail":"invalid JSON"}) или пустое name ({"detail":"name is required"});
    • 405 Method Not Allowed — если дернуть не POST.
  • GET /ping{"message":"pong"} (быстрая проверка JSON-ответа).

  • GET /notes — вернуть список заметок.

  • GET /note/{id} — вернуть заметку по id.

  • DELETE /note/{id} — удалить заметку по id.

Модель данных

  "id":   1,           // назначается сервером
  "name": "string",    // обязательно
  "text": "string"     // опционально
}

Как запустить

go run .
# сервер слушает http://localhost:8000

API и примеры запросов (curl)

Проверка жизни

curl http://localhost:8000/
# Hello, Go server!

Создать заметку (успех)

curl -i -X POST http://localhost:8000/note \
  -H 'Content-Type: application/json' \
  -d '{"name":"first","text":"hello go"}'

Ожидаемо:

HTTP/1.1 201 Created
Content-Type: application/json
{"name":"first","text":"hello go"}

Пустое имя (валидация)

curl -i -X POST http://localhost:8000/note \
  -H 'Content-Type: application/json' \
  -d '{"name":"","text":"no name"}'

Ответ:

HTTP/1.1 400 Bad Request
{"detail":"name is required"}

Не-JSON

curl -i -X POST http://localhost:8000/note \
  -H 'Content-Type: application/json' \
  -d 'this is not json'

Ответ:

HTTP/1.1 400 Bad Request
{"detail":"invalid JSON"}

Получить список

curl -i http://localhost:8000/note/1
# HTTP/1.1 200 OK
# {"id":1,"name":"first","text":"hello"}

Получить заметку по id

curl -i http://localhost:8000/note/1
# HTTP/1.1 200 OK
# {"id":1,"name":"first","text":"hello"}

Удалить заметку

curl -i -X DELETE http://localhost:8000/note/1
# HTTP/1.1 200 OK
# {"status":"ok"}

curl -i -X DELETE http://localhost:8000/note/1
# HTTP/1.1 404 Not Found
# {"detail":"Note not found"}


Полезные приёмы работы с curl

  • -i — показать статус и заголовки в ответе.
  • -v — подробный режим (видно, что реально ушло/пришло).
  • -H 'Content-Type: application/json' — обязательно для JSON-тел.
  • -d '…' — тело запроса. Используй одинарные кавычки '…' , а не обратные — те запускают команду в bash.
  • Перенос строки \ допустим, но после слеша не должно быть пробелов

net/http — сервер и маршрутизатор (http.NewServeMux).

  • Обработчики имеют сигнатуру:

    func(w http.ResponseWriter, r *http.Request)

    где w — «куда писать ответ», r — «что пришло».

  • JSON: пакет encoding/json (json.NewDecoder(r.Body).Decode(&in) / json.NewEncoder(w).Encode(out)).

  • Модель данных (ожидаемый JSON):

    type Note struct {
        Name string `json:"name"`
        Text string `json:"text"`
    }
  • Статусы: http.StatusCreated (201), http.StatusBadRequest (400), http.StatusMethodNotAllowed (405) и т.д.


    Как это устроено

    • Маршрутизаторhttp.NewServeMux(). Регистрируем пути через HandleFunc.

    • Хендлер — обычная функция

      func(w http.ResponseWriter, r *http.Request)

      где r — всё про запрос, w — куда писать ответ.

    • JSON :

    • чтение: json.NewDecoder(r.Body).Decode(&in)

    • запись: json.NewEncoder(w).Encode(out)

    • Память/БД :

      type notesDB struct {
          mu   sync.RWMutex // замок
          data []Note       // "таблица" заметок
          next int          // автоинкремент id
      }
    • для чтения : RLock() / RUnlock() — много одновременных читателей OK;
    • для записи : Lock() / Unlock() — только один писатель;
    • в list() отдаём копию среза, чтобы внешние изменения не портили внутреннее состояние:
      out := make([]Note, len(db.data))
      copy(out, db.data)
      return out
    • Парсинг {id} из пути : отрезаем префикс "/note/", убираем хвостовой /, преобразуем строку в число (strconv.Atoi). Если не вышло — 404.

    Типовые статусы

    • 201 Created — создан ресурс (POST /note), в заголовке Location: /note/{id}.
    • 200 OK — обычные успешные ответы.
    • 400 Bad Request — невалидный JSON или нарушена валидация.
    • 404 Not Found — заметка не найдена (неверный id).
    • 405 Method Not Allowed — метод не подходит для маршрута.

Что сделали

  • Подняли PostgreSQL в Docker (db), проверили соединение.
  • Применили миграцию 0001_init.up.sql → таблица public.notes и индекс idx_notes_name созданы.
  • Запустили и проверили check.sql:
    • INSERT → UPDATE → EXPLAIN ANALYZE → DELETE, индекс используется (Bitmap Index Scan).
  • Добавили удобные цели в Makefile: db-up, migrate-up, migrate-down, sql-check.
  • Подготовили Go-сервис к работе с БД:
    • Ввели абстракцию Store и реализацию на Postgres (pgStore), сохранили in-memory как fallback.
    • Обновили хендлеры (create/list/update/delete) под Store.
  • Провели смоук-тесты curl и прямые проверки через psql.

Что улучшить

  1. Единый источник данных:

    • Либо навсегда включить Postgres-режим (требовать DB_DSN),
    • Либо оставить dual-mode, но явно логировать, какой Store активен.
  2. Корректная обработка no rows:

    Использовать errors.Is(err, pgx.ErrNoRows) вместо парсинга строки ошибок.

  3. UPDATE … RETURNING:

    Возвращать из БД фактическую запись (а не присланный JSON), чтобы клиент получал правду при частичных апдейтах.

  4. Грациозное закрытие пула БД:

    При остановке сервера вызывать pgPool.Close().

  5. Docker Compose:

    • Удалить устаревшее поле version: (предупреждение в логах).
    • Для app явно задать DB_DSN=postgres://notes:notes@db:5432/notesdb?sslmode=disable и проброс 8000:8000.
    • БД наружу оставить на 5433:5432 (внутри сети app ходит на db:5432).
  6. Конфиг и запуск:

    • .env с переменными (DB_DSN, порт, лог-уровень).
    • Цели make run, make logs, make down, healthcheck в compose.
  7. Валидация и ответы:

    • Свести всю проверку входных данных в validate*, чтобы не дублировать логику.
    • Единый формат ошибок {"detail":"…"} (у тебя уже почти так).
  8. Миграции в рантайме (опционально):

    Подключить pressly/goose или golang-migrate и прогонять миграции при старте app.

  9. Наблюдаемость (опционально):

    Структурные логи (zerolog/slog), таймауты контекстов для запросов к БД, /health endpoint.

  10. Документация и тесты (по мере времени):

  • Мини-README со сценариями запуска.
  • httptest для хендлеров и интеграционный тест к БД (dockerized).

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages