CRUD API на Go с Gin и MongoDB

Golang — один из высокооплачиваемых и востребованных языков программирования. В сочетании с фреймворками вроде Gin, Revel или gorilla/mux Go отлично подходит для создания API. В этом руководстве вы шаг за шагом создадите CRUD API на Go с использованием Gin и MongoDB.
Что вы получите из этого руководства
- Рабочий CRUD API на Go (создать, прочитать, обновить, удалить).
- Пример структуры проекта и кода для реального приложения.
- Советы по безопасности, тестированию и деплою.
Начальная настройка и установка
Установите Golang на вашу машину, если он ещё не установлен. Затем создайте корневую папку проекта и инициализируйте модуль Go в этой папке.
Откройте терминал, перейдите в корень проекта и выполните:
go mod init module_nameОткройте файл go.mod — вы увидите имя вашего модуля (например, CRUD_API). Все собственные пакеты будут импортироваться от этого модуля, например:
import "CRUD_API/package-directory-name"Установите зависимости. Для роутинга используем Gin:
go get github.com/gin-gonic/ginДля работы с MongoDB установите официальный драйвер:
go get go.mongodb.org/mongo-driver/mongoВажно: храните конфиденциальные строки подключения (URI) в .env или в системе управления секретами — не в коде.
Как подключить Go к MongoDB
Для подключения требуется MongoDB URI. Пример локального подключения к MongoDB:
Mongo_URL = "mongodb://127.0.0.1:27017"Создайте папку databases и файл database.go — пакет для подключения к базе. Ниже пример функции подключения:
package database
import (
"context"
"fmt"
"log"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func ConnectDB() *mongo.Client {
Mongo_URL := "mongodb://127.0.0.1:27017"
client, err := mongo.NewClient(options.Client().ApplyURI(Mongo_URL))
if err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second)
err = client.Connect(ctx)
defer cancel()
if err != nil {
log.Fatal(err)
}
fmt.Println("Connected to mongoDB")
return client
}Определение: Context — структурированный способ задавать таймауты и отмены операций; primitive.ObjectID — тип идентификатора документа в MongoDB.
Создание коллекции
MongoDB хранит данные в коллекциях. Создайте папку Collection и файл getCollection.go, который вернёт ссылку на коллекцию в базе:
package getcollection
import (
"go.mongodb.org/mongo-driver/mongo"
)
func GetCollection(client *mongo.Client, collectionName string) *mongo.Collection {
collection := client.Database("myGoappDB").Collection("Posts")
return collection
}Здесь база называется myGoappDB, коллекция — Posts. При желании вынесите имя БД и коллекции в константы или конфиг.
Создание модели данных
Создайте папку model и файл model.go с определением структуры поста:
package model
import (
"go.mongodb.org/mongo-driver/bson/primitive"
)
type Post struct {
ID primitive.ObjectID
Title string
Article string
}Совет: добавляйте теги BSON/JSON к полям модели для надёжного маршалинога/анмаршалинога (например, bson:"_id,omitempty" json:"id"). Это облегчает сериализацию и работу с клиентами.
Создание CRUD API на Go
Создайте папку routes; в ней — файлы create.go, read.go, update.go, delete.go. Каждый файл экспортирует обработчик в пакете routes.
POST: создание записи
Файл routes/create.go — обработчик создания нового поста:
package routes
import (
getcollection "CRUD_API/Collection"
database "CRUD_API/databases"
model "CRUD_API/model"
"context"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func CreatePost(c *gin.Context) {
var DB = database.ConnectDB()
var postCollection = getcollection.GetCollection(DB, "Posts")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
post := new(model.Posts)
defer cancel()
if err := c.BindJSON(&post); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": err})
log.Fatal(err)
return
}
postPayload := model.Posts{
Id: primitive.NewObjectID(),
Title: post.Title,
Article: post.Article,
}
result, err := postCollection.InsertOne(ctx, postPayload)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": err})
return
}
c.JSON(http.StatusCreated, gin.H{"message": "Posted successfully", "Data": map[string]interface{}{"data": result}})
}Примечание: проверяйте ошибки BindJSON и не используйте log.Fatal в обработчиках — лучше возвращать корректный HTTP-ответ и логировать отдельно.
GET: чтение одной записи
Файл routes/read.go — чтение документа по id:
package routes
import (
getcollection "CRUD_API/Collection"
database "CRUD_API/databases"
model "CRUD_API/model"
"context"
"net/http"
"time"
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func ReadOnePost(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
var DB = database.ConnectDB()
var postCollection = getcollection.GetCollection(DB, "Posts")
postId := c.Param("postId")
var result model.Posts
defer cancel()
objId, _ := primitive.ObjectIDFromHex(postId)
err := postCollection.FindOne(ctx, bson.M{"id": objId}).Decode(&result)
res := map[string]interface{}{"data": result}
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": err})
return
}
c.JSON(http.StatusCreated, gin.H{"message": "success!", "Data": res})
}Совет: используйте корректные поля фильтрации — обычно идентификатор в MongoDB хранится в поле _id, а не id.
PUT: обновление записи
Файл routes/update.go — обновление документа по id:
package routes
import (
getcollection "CRUD_API/Collection"
database "CRUD_API/databases"
model "CRUD_API/model"
"context"
"net/http"
"time"
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func UpdatePost(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second)
var DB = database.ConnectDB()
var postCollection = getcollection.GetCollection(DB, "Posts")
postId := c.Param("postId")
var post model.Posts
defer cancel()
objId, _ := primitive.ObjectIDFromHex(postId)
if err := c.BindJSON(&post); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": err})
return
}
edited := bson.M{"title": post.Title, "article": post.Article}
result, err := postCollection.UpdateOne(ctx, bson.M{"id": objId}, bson.M{"$set": edited})
res := map[string]interface{}{"data": result}
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": err})
return
}
if result.MatchedCount < 1 {
c.JSON(http.StatusInternalServerError, gin.H{"message": "Data doesn't exist"})
return
}
c.JSON(http.StatusCreated, gin.H{"message": "data updated successfully!", "Data": res})
}Обратите внимание: проверяйте matched/modified count, чтобы убедиться, что обновление действительно произошло.
DELETE: удаление записи
Файл routes/delete.go — удаление документа по id:
package routes
import (
getcollection "CRUD_API/Collection"
database "CRUD_API/databases"
"context"
"net/http"
"time"
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func DeletePost(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
var DB = database.ConnectDB()
postId := c.Param("postId")
var postCollection = getcollection.GetCollection(DB, "Posts")
defer cancel()
objId, _ := primitive.ObjectIDFromHex(postId)
result, err := postCollection.DeleteOne(ctx, bson.M{"id": objId})
res := map[string]interface{}{"data": result}
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": err})
return
}
if result.DeletedCount < 1 {
c.JSON(http.StatusInternalServerError, gin.H{"message": "No data to delete"})
return
}
c.JSON(http.StatusCreated, gin.H{"message": "Article deleted successfully", "Data": res})
}Проверьте, что используете корректное поле _id и корректно обрабатываете ошибки преобразования hex->ObjectID.
Файл запускa main.go
Проект должен иметь примерно такую структуру (файловая схема показана ниже):
main.go отвечает за запуск HTTP-сервера и привязку маршрутов:
package main
import (
routes "CRUD_API/routes"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.POST("/", routes.CreatePost)
// called as localhost:3000/getOne/{id}
router.GET("getOne/:postId", routes.ReadOnePost)
// called as localhost:3000/update/{id}
router.PUT("/update/:postId", routes.UpdatePost)
// called as localhost:3000/delete/{id}
router.DELETE("/delete/:postId", routes.DeletePost)
router.Run("localhost: 3000")
}Запуск сервера из корня проекта:
go run main.goПосле запуска сервер будет доступен на localhost:3000.
Важно: для продакшена используйте переменные окружения и конфигурационные файлы, настройте TLS и корректный адрес прослушивания (обычно :80 или :443, либо порт, указанный в конфиге).
Лучшие практики и рекомендации
- Валидация входных данных: используйте binding/валидацию в Gin и проверяйте обязательные поля.
- Логирование: централизованное логирование (например, zap, logrus). Не выводите секреты в логи.
- Контексты и таймауты: каждый запрос должен иметь контекст с таймаутом.
- Используйте
_idкак поле для поиска в MongoDB. - Миграции и индексы: создавайте индексы для часто используемых полей.
- Обработка ошибок: не возвращайте пользователю «сырой» error; формируйте понятные сообщения и коды статусов.
Безопасность и hardening
- Храните строку подключения в защищённом хранилище (Vault, Secrets Manager или .env на этапе разработки).
- Включите аутентификацию и авторизацию (JWT, OAuth) для эндпоинтов, которые изменяют данные.
- Настройте CORS (если вызываете API из браузера).
- В продакшне используйте TLS и ограничьте доступ к MongoDB по IP/ACL.
Когда такая архитектура не подходит
- Если вам нужен сложный транзакционный контроль между коллекциями — SQL или распределённые транзакции могут подойти лучше.
- Если требуется сложная аналитика и агрегации на больших объёмах данных — рассмотрите OLAP-решения.
Альтернативы
- Web-фреймворки: Echo, Fiber, stdlib net/http.
- БД: PostgreSQL (sqlx, gorm), Redis (для кэша), Cassandra для масштабирования по записи.
Ментальные модели (heuristics)
- Keep it simple: сначала минимально рабочая API-логика, затем оптимизируйте.
- Fail fast: проверяйте вход и возвращайте ошибку сразу.
- Single responsibility: каждый обработчик делает одну работу (CRUD операцию).
Решение: как выбрать стек (decision tree)
flowchart TD
A[Нужен быстрый API] --> B{Требуется ли транзакционность?}
B -- Да --> C[Рассмотрите SQL 'Postgres']
B -- Нет --> D{Нужна горизонтальная масштабируемость записей?}
D -- Да --> E[MongoDB / Cassandra]
D -- Нет --> F[MongoDB или Postgres — по предпочтению]Чек-листы по ролям
Developer:
- Локально работает сервер
- Тесты покрывают базовые сценарии CRUD
- Конфигурация вынесена в .env
DevOps:
- Секреты вынесены в секрет-менеджер
- Настроен CI/CD (build -> test -> deploy)
- Мониторинг и alerting
QA:
- Тест-кейсы для позитивных и негативных сценариев
- Тесты на авторизацию/валидацию
Краткий SOP для деплоя
- Собрать бинарник (go build).
- Выполнить миграции/создать индексы в MongoDB.
- Настроить переменные окружения.
- Запустить сервис в контейнере/сервере.
- Прогнать smoke-tests.
- Переключить трафик.
Тест-кейсы (основные)
- Create: POST / — валидный payload -> 201 и документ в БД.
- Read: GET /getOne/:id — существующий id -> 200 и корректный документ.
- Update: PUT /update/:id — существующий id и валидный payload -> 200 и изменения в БД.
- Delete: DELETE /delete/:id — существующий id -> 200 и запись удалена.
- Error: GET с невалидным id -> 400/404.
Критерии приёмки
- Все CRUD эндпоинты возвращают корректные HTTP-коды.
- Запросы с некорректными данными возвращают описанную ошибку.
- Сервер стабильно обрабатывает базовые сценарии в течение 10 минут под нагрузкой (smoke test).
Мини-глоссарий
- CRUD — Create, Read, Update, Delete.
- URI — строка подключения к MongoDB.
- Context — механизм контроля времени выполнения операций в Go.
Когда это не сработает / Ограничения
- Невозможность обеспечить сильную согласованность между несколькими документами без транзакций.
- При больших объёмах данных потребуется продуманная схема индексации и шардирование.
Маленькая методология разработки
- Определите модель данных и API контракт (OpenAPI/Swagger).
- Реализуйте минимальные обработчики (happy path).
- Добавьте валидацию и обработку ошибок.
- Покройте тестами.
- Подготовьте CI/CD и секреты.
Заключение
Вы создали базовый CRUD API на Go с использованием Gin и MongoDB и получили набор практических рекомендаций для его улучшения и подготовки к продакшену. Следующим шагом можно добавить аутентификацию, валидацию схемы через OpenAPI и логирование с централизованным сбором метрик.
Сводка
- Создайте модуль Go и установите Gin и MongoDB драйвер.
- Настройте подключение к MongoDB через пакет database.
- Опишите модель, реализуйте обработчики CRUD и запускающий main.
- Применяйте лучшие практики безопасности, тестирования и деплоя.
Спасибо за внимание — развивайте проект: добавляйте аутентификацию, пагинацию, фильтрацию и документацию API.