Создание 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 — запуск сервера
- Советы по безопасности, тестам и альтернативные подходы
- Контроль приёмки и чеклисты
Начальная настройка и установка
- Установите Go (https://go.dev) и убедитесь, что GOPATH/GO111MODULE настроены.
- Создайте корневую папку проекта и инициализируйте модуль:
go mod init CRUD_API- Установите 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})
}
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 или другая реляционная БД будет предпочтительнее.
Как дальше развивать проект (руководство)
Короткая методология:
- Спроектируйте модель данных и API (OpenAPI/Swagger).
- Реализуйте модель и базовые CRUD-эндпойнты.
- Добавьте валидацию и аутентификацию.
- Напишите тесты (unit + integration).
- Настройте CI/CD и мониторинг.
- Разверните в staging и прогоните нагрузочное тестирование.
Короткое резюме
- Вы создали рабочий CRUD API на Go с использованием Gin и MongoDB.
- Кодовые примеры покрывают подключение к БД, модель Post и обработчики Create/Read/Update/Delete.
- Рекомендации по безопасности, тестированию и чеклисты помогут довести проект до production-готовности.
Extras — краткие выводы:
- Используйте переменные окружения для конфиденциальных данных.
- Всегда валидации входные данные и обрабатывайте ошибки корректно.
- Продумывайте контракт API (формат ответов и статусы) заранее.
Спасибо — теперь у вас есть база для расширяемого сервиса на Go. Удачной разработки!
Похожие материалы
Установка GitHub CLI на Linux
Как установить Epic Games и играть на Linux
Как сделать Stitch в TikTok — полное руководство
TEXTSPLIT, TEXTBEFORE, TEXTAFTER в Excel
Изменение значков и цветов в приложении «Дом»