Гид по технологиям

Создание CRUD API на Go с Gin и MongoDB

5 min read Backend Обновлено 08 Apr 2026
CRUD API на Go с Gin и MongoDB
CRUD API на Go с Gin и MongoDB

Редактор кода с блоками кода

О чём эта инструкция

  • Цель: показать минимальную, корректную и расширяемую реализацию CRUD API на Go с Gin и MongoDB.
  • Для кого: разработчики backend, начинающие в Go или желающие получить готовую основу для проекта.
  • Результат: проект, который запускается локально и обрабатывает POST/GET/PUT/DELETE запросы к коллекции Posts.

Important: кодовые примеры приведены в рабочем виде — их можно скопировать, поправить переменные окружения и запустить.

Структура статьи

  • Установка и инициализация модуля Go
  • Подключение к MongoDB
  • Модель данных
  • Роуты и обработчики (Create, Read, Update, Delete)
  • main.go — запуск сервера
  • Советы по безопасности, тестам и альтернативные подходы
  • Контроль приёмки и чеклисты

Начальная настройка и установка

  1. Установите Go (https://go.dev) и убедитесь, что GOPATH/GO111MODULE настроены.
  2. Создайте корневую папку проекта и инициализируйте модуль:
go mod init CRUD_API
  1. Установите Gin и MongoDB драйвер:
go get github.com/gin-gonic/gin
go get go.mongodb.org/mongo-driver/mongo

Примечание: в production храните URL базы и секреты в переменных окружения или в .env (через пакет dotenv).

Как подключить Go к MongoDB

Создайте папку 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)
    defer cancel()

    if err := client.Connect(ctx); err != nil {
        log.Fatal(err)
    }

    // Дополнительно можно выполнить Ping для проверки соединения
    if err := client.Ping(ctx, nil); err != nil {
        log.Fatal(err)
    }

    fmt.Println("Connected to mongoDB")
    return client
}

Короткое пояснение: ConnectDB возвращает *mongo.Client, который вы используете для получения коллекций. В production строка подключения не должна быть в коде.

Создание функции получения коллекции

Папка Collection с файлом getCollection.go:

package getcollection

import (
    "go.mongodb.org/mongo-driver/mongo"
)

func GetCollection(client *mongo.Client, collectionName string) *mongo.Collection {
    return client.Database("myGoappDB").Collection(collectionName)
}

Это небольшая обёртка — передаёте имя коллекции и получаете объект *mongo.Collection.

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

Папка model и файл model.go. Определяем тип Post с BSON/JSON тегами, чтобы корректно работать и с MongoDB, и с JSON в запросах/ответах:

package model

import (
    "go.mongodb.org/mongo-driver/bson/primitive"
)

type Post struct {
    ID      primitive.ObjectID `bson:"_id,omitempty" json:"id"`
    Title   string             `bson:"title" json:"title"`
    Article string             `bson:"article" json:"article"`
}

1-line glossary: ObjectID — уникальный идентификатор документа в MongoDB.

Создание 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"
    "net/http"
    "time"

    "github.com/gin-gonic/gin"
    "go.mongodb.org/mongo-driver/bson/primitive"
)

func CreatePost(c *gin.Context) {
    DB := database.ConnectDB()
    postCollection := getcollection.GetCollection(DB, "Posts")

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    post := new(model.Post)
    if err := c.BindJSON(post); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
        return
    }

    post.ID = primitive.NewObjectID()

    result, err := postCollection.InsertOne(ctx, post)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
        return
    }

    c.JSON(http.StatusCreated, gin.H{"message": "Posted successfully", "data": result})
}

Ключевые моменты: используем BindJSON для валидации JSON по структуре Post; генерируем ObjectID и вставляем документ.

GET — чтение одной записи по ID

routes/read.go:

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)
    defer cancel()

    DB := database.ConnectDB()
    postCollection := getcollection.GetCollection(DB, "Posts")

    postId := c.Param("postId")
    objId, err := primitive.ObjectIDFromHex(postId)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"message": "invalid id"})
        return
    }

    var result model.Post
    if err := postCollection.FindOne(ctx, bson.M{"_id": objId}).Decode(&result); err != nil {
        c.JSON(http.StatusNotFound, gin.H{"message": "not found"})
        return
    }

    c.JSON(http.StatusOK, gin.H{"message": "success", "data": result})
}

Здесь важно искать по полю _id (BSON) и корректно обрабатывать ошибку парсинга ObjectID.

PUT — обновление записи

routes/update.go:

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)
    defer cancel()

    DB := database.ConnectDB()
    postCollection := getcollection.GetCollection(DB, "Posts")

    postId := c.Param("postId")
    objId, err := primitive.ObjectIDFromHex(postId)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"message": "invalid id"})
        return
    }

    var post model.Post
    if err := c.BindJSON(&post); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
        return
    }

    edited := bson.M{"title": post.Title, "article": post.Article}

    result, err := postCollection.UpdateOne(ctx, bson.M{"_id": objId}, bson.M{"$set": edited})
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
        return
    }

    if result.MatchedCount < 1 {
        c.JSON(http.StatusNotFound, gin.H{"message": "Data doesn't exist"})
        return
    }

    c.JSON(http.StatusOK, gin.H{"message": "data updated successfully", "data": result})
}

Возвращаем 404, если ничего не найдено, и 200 при успешном обновлении.

DELETE — удаление записи

routes/delete.go:

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)
    defer cancel()

    DB := database.ConnectDB()
    postCollection := getcollection.GetCollection(DB, "Posts")

    postId := c.Param("postId")
    objId, err := primitive.ObjectIDFromHex(postId)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"message": "invalid id"})
        return
    }

    result, err := postCollection.DeleteOne(ctx, bson.M{"_id": objId})
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
        return
    }

    if result.DeletedCount < 1 {
        c.JSON(http.StatusNotFound, gin.H{"message": "No data to delete"})
        return
    }

    c.JSON(http.StatusOK, gin.H{"message": "Article deleted successfully", "data": result})
}

Структура проекта Golang CRUD

main.go — запуск API

Создайте main.go в корне проекта:

package main

import (
    routes "CRUD_API/routes"
    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()

    router.POST("/", routes.CreatePost)
    router.GET("/getOne/:postId", routes.ReadOnePost)
    router.PUT("/update/:postId", routes.UpdatePost)
    router.DELETE("/delete/:postId", routes.DeletePost)

    router.Run("localhost:3000")
}

Запуск сервера:

go run main.go

После запуска доступ к API по адресу http://localhost:3000


Когда такой подход подходит и когда нет

  • Подходит: быстрые микросервисы, прототипы, проекты с нестрогой схемой данных, небольшие веб-приложения.
  • Не подходит: требовательные к транзакциям системы (сложные ACID-требования), аналитические нагрузки, когда нужна сильная схема/взаимосвязи (лучше реляционные БД).

Альтернативные подходы

  • Другие фреймворки: Echo, Fiber, стандартный net/http. Fiber часто быстрее в обработке большого числа соединений.
  • Другая СУБД: PostgreSQL с GORM/SQLX для сложных запросов и транзакций.
  • ORM: использовать ent, GORM или sqlc, если нужно статическое моделирование схемы.

Модель мышления и эвристики для REST-кода

  • Используйте корректные HTTP-статусы: 201 для создания, 200 для успешных операций, 400 для ошибок клиента, 404 для не найдено, 500 для внутренних ошибок.
  • Идемпотентность: PUT должен быть идемпотентным; POST — нет.
  • Валидация входных данных — первая линия защиты.

Безопасность и харднинг

  • Секреты: храните URL БД и креды в переменных окружения.
  • Валидация: проверяйте и ограничивайте длину и формат полей (например, Title не длиннее 255 символов).
  • Ограничение скорости: добавьте rate-limiter для публичных эндпойнтов.
  • Логи: не записывайте секреты в логи.
  • Разграничение прав: если приложение многопользовательское, проверяйте авторизацию перед Update/Delete.

Тесты и критерии приёмки

Критерии приёмки (Acceptance criteria):

  • POST / создаёт документ и возвращает 201 с id нового документа.
  • GET /getOne/{id} возвращает 200 и структуру Post при корректном id, 404 — если нет.
  • PUT /update/{id} обновляет существующую запись; 404 если нет.
  • DELETE /delete/{id} удаляет запись; 404 если нет.

Минимальные тест-кейсы:

  • Создать запись -> убедиться, что ID валидный и документ сохранён.
  • Попытка получить несуществующий ID -> 404.
  • Обновить существующий документ -> поля изменились.
  • Удалить документ -> при следующем GET 404.

Ролевые чеклисты (Dev / QA / Ops)

Dev:

  • Добавить валидацию входных данных
  • Логировать ошибки и статусные коды
  • Скрыть переменные окружения

QA:

  • Написать unit и integration тесты для всех эндпойнтов
  • Тестировать граничные значения полей
  • Проверить обработку некорректных ID

Ops:

  • Настроить CI для запуска тестов
  • Настроить мониторинг (ошибки, загрузка, SLI)
  • Настроить бэкап БД и секретов

Примеры когда решение не подходит (контрпример)

Если вашему приложению нужны сложные JOIN-операции и строгая целостность ссылок, MongoDB может усложнить реализацию. В таких случаях PostgreSQL или другая реляционная БД будет предпочтительнее.

Как дальше развивать проект (руководство)

Короткая методология:

  1. Спроектируйте модель данных и API (OpenAPI/Swagger).
  2. Реализуйте модель и базовые CRUD-эндпойнты.
  3. Добавьте валидацию и аутентификацию.
  4. Напишите тесты (unit + integration).
  5. Настройте CI/CD и мониторинг.
  6. Разверните в staging и прогоните нагрузочное тестирование.

Короткое резюме

  • Вы создали рабочий CRUD API на Go с использованием Gin и MongoDB.
  • Кодовые примеры покрывают подключение к БД, модель Post и обработчики Create/Read/Update/Delete.
  • Рекомендации по безопасности, тестированию и чеклисты помогут довести проект до production-готовности.

Extras — краткие выводы:

  • Используйте переменные окружения для конфиденциальных данных.
  • Всегда валидации входные данные и обрабатывайте ошибки корректно.
  • Продумывайте контракт API (формат ответов и статусы) заранее.

Спасибо — теперь у вас есть база для расширяемого сервиса на Go. Удачной разработки!

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Установка GitHub CLI на Linux
Разработка

Установка GitHub CLI на Linux

Как установить Epic Games и играть на Linux
Linux

Как установить Epic Games и играть на Linux

Как сделать Stitch в TikTok — полное руководство
Социальные сети

Как сделать Stitch в TikTok — полное руководство

TEXTSPLIT, TEXTBEFORE, TEXTAFTER в Excel
Excel

TEXTSPLIT, TEXTBEFORE, TEXTAFTER в Excel

Изменение значков и цветов в приложении «Дом»
Умный дом

Изменение значков и цветов в приложении «Дом»

Исправить уведомления WhatsApp в Windows 10
Технологии

Исправить уведомления WhatsApp в Windows 10