
Введение
В данной статье будет рассмотрена практическая интеграция 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 для документации.
Комментарии (2)
Rom4io
07.08.2025 14:30Итак, Вы увидели, как ИИ может создавать статьи.
Автор, включи в промпт, что бы было описание, что в коде происходит, иначе непонятно, на кого рассчитана статья, новичкам непонятна, знающим скучно
yellow79
Не понятен выбор в пользу Gorilla Mux, оно ничего не даёт, кроме зависимости. Стандартный http.ServeMux умеет точно так же.
zerolog и zap давно в прошлом, стандартный log/slog ничем им не уступает