Построение REST API на Go с использованием Gorilla Mux и MongoDB

от автора

go lang

go lang

Введение

В данной статье будет рассмотрена практическая интеграция MongoDB с веб-приложением на Go, построенным на базе маршрутизатора Gorilla Mux. Цель — получить минимальный, но функциональный REST API с поддержкой CRUD-операций над сущностью Book, при этом соблюдая лучшие практики структурирования кода.

Материал рассчитан на разработчиков, знакомых с Go, HTTP API и основами работы с базами данных.

Выбор стека

Go — компилируемый язык с лаконичным синтаксисом, встроенной поддержкой параллелизма и богатой стандартной библиотекой для работы с сетью. Эти качества делают его удобным выбором для разработки API-сервисов.

Почему Gorilla Mux

Gorilla Mux — зрелый, широко используемый HTTP-роутер для Go, который поддерживает:

  • маршрутизацию с шаблонами (/books/{id});

  • фильтрацию по HTTP-методу;

  • middleware.

Почему MongoDB

MongoDB — документно-ориентированная NoSQL-БД, оптимизированная для хранения неструктурированных или слабо структурированных данных, что делает её подходящей для быстрого прототипирования и гибких схем.

Архитектура проекта

Структура каталогов для примера:

/cmd     /api         main.go /internal     /books         handler.go         model.go         repository.go /pkg     /db         mongo.go go.mod 

Такое разделение позволяет изолировать доменную логику (internal/books) от инфраструктурных компонентов (pkg/db).

Подготовка окружения

Установка MongoDB (Linux, apt)

sudo apt update sudo apt install -y mongodb

После установки убедитесь, что сервис запущен:

systemctl status mongodb

Подключение к shell:

  mongo 

Cоздадим базу данных booksdb и коллекцию books:

use booksdb db.books.insertOne({   title: "A Song of Ice and Fire",   authors: ["George R.R. Martin", "Phyllis Eisenstein"],   publish_date: ISODate("1996-08-01"),   publisher: {     name: "Bantam Books",     country: "US"   } }) 

Подключение зависимостей

go mod init github.com/username/mongo-gorilla-api go get github.com/gorilla/mux go get go.mongodb.org/mongo-driver/mongo go get go.mongodb.org/mongo-driver/mongo/options 

Примечание: В отличие от устаревшего mgo.v2, рекомендуется использовать официальный mongo-driver.

Инициализация подключения к MongoDB

/pkg/db/mongo.go:

package db  import (     "context"     "log"     "time"      "go.mongodb.org/mongo-driver/mongo"     "go.mongodb.org/mongo-driver/mongo/options" )  func Connect(uri string) *mongo.Database {     ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)     defer cancel()      client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri))     if err != nil {         log.Fatalf("Ошибка подключения к MongoDB: %v", err)     }      if err := client.Ping(ctx, nil); err != nil {         log.Fatalf("MongoDB не отвечает: %v", err)     }      log.Println("Соединение с MongoDB установлено")     return client.Database("booksdb") } 

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

/internal/books/model.go:

package books  import "go.mongodb.org/mongo-driver/bson/primitive"  type Book struct {     ID          primitive.ObjectID `bson:"_id,omitempty" json:"id"`     Title       string             `bson:"title" json:"title"`     Authors     []string           `bson:"authors" json:"authors"`     PublishDate string             `bson:"publish_date" json:"publish_date"`     Publisher   Publisher          `bson:"publisher" json:"publisher"` }  type Publisher struct {     Name    string `bson:"name" json:"name"`     Country string `bson:"country" json:"country"` } 

Репозиторий

/internal/books/repository.go:

package books  import (     "context"     "time"      "go.mongodb.org/mongo-driver/bson"     "go.mongodb.org/mongo-driver/mongo" )  type Repository struct {     col *mongo.Collection }  func NewRepository(db *mongo.Database) *Repository {     return &Repository{col: db.Collection("books")} }  func (r *Repository) FindAll(ctx context.Context) ([]Book, error) {     var books []Book     cur, err := r.col.Find(ctx, bson.D{})     if err != nil {         return nil, err     }     defer cur.Close(ctx)      for cur.Next(ctx) {         var book Book         if err := cur.Decode(&book); err != nil {             return nil, err         }         books = append(books, book)     }     return books, nil }  func (r *Repository) Insert(ctx context.Context, book Book) (*mongo.InsertOneResult, error) {     ctx, cancel := context.WithTimeout(ctx, 5*time.Second)     defer cancel()     return r.col.InsertOne(ctx, book) } 

HTTP-обработчики

/internal/books/handler.go:

package books  import (     "context"     "encoding/json"     "net/http"     "time" )  type Handler struct {     repo *Repository }  func NewHandler(repo *Repository) *Handler {     return &Handler{repo: repo} }  func (h *Handler) GetBooks(w http.ResponseWriter, r *http.Request) {     ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)     defer cancel()      books, err := h.repo.FindAll(ctx)     if err != nil {         http.Error(w, err.Error(), http.StatusInternalServerError)         return     }      w.Header().Set("Content-Type", "application/json")     json.NewEncoder(w).Encode(books) }  func (h *Handler) CreateBook(w http.ResponseWriter, r *http.Request) {     var book Book     if err := json.NewDecoder(r.Body).Decode(&book); err != nil {         http.Error(w, err.Error(), http.StatusBadRequest)         return     }      ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)     defer cancel()      _, err := h.repo.Insert(ctx, book)     if err != nil {         http.Error(w, err.Error(), http.StatusInternalServerError)         return     }      w.WriteHeader(http.StatusCreated) } 

Точка входа

/cmd/api/main.go:

package main  import (     "log"     "net/http"      "github.com/gorilla/mux"     "github.com/username/mongo-gorilla-api/internal/books"     "github.com/username/mongo-gorilla-api/pkg/db" )  func main() {     database := db.Connect("mongodb://localhost:27017")      repo := books.NewRepository(database)     handler := books.NewHandler(repo)      r := mux.NewRouter()     r.HandleFunc("/books", handler.GetBooks).Methods("GET")     r.HandleFunc("/books", handler.CreateBook).Methods("POST")      log.Println("Сервер запущен на :8080")     log.Fatal(http.ListenAndServe(":8080", r)) } 

Проверка API

Создание книги:

curl -X POST http://localhost:8080/books \      -H "Content-Type: application/json" \      -d '{"title":"Go in Action","authors":["William Kennedy"],"publish_date":"2015-11-26","publisher":{"name":"Manning","country":"US"}}' 

Получение списка:

curl http://localhost:8080/books 

Заключение

В результате мы получили минималистичный, но масштабируемый REST API на Go, интегрированный с MongoDB. Использование официального драйвера и структурированного подхода к архитектуре позволяет легко расширять сервис, добавляя валидацию, аутентификацию и более сложные бизнес-правила.

В дальнейшем можно:

  • Вынести конфигурацию в .env и использовать viper или envconfig;

  • Добавить логирование (zerolog, zap);

  • Реализовать graceful shutdown;

  • Подключить Swagger/OpenAPI для документации.


ссылка на оригинал статьи https://habr.com/ru/articles/935102/