Создание REST API в Go с интеграцией MongoDB: пошаговое руководство

Создание REST API в Go с интеграцией MongoDB: пошаговое руководство

3 марта 2023 г.

В этой статье я покажу вам, как написать REST API на Go и интегрировать MongoDB в качестве вашей базы данных. Вы будете использовать драйвер MongoDB для создания Note API, который выполняет четыре основные операции: Create-Retrieve-Update-Delete с внутренними библиотеками Go.

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

:::информация Примечание. Я использую операционную систему Windows 10.

:::

Настройка среды разработки

Давайте создадим нашу среду разработки, открыв командную строку и создав папку. Я называю проект tautpad, так как taut означает лаконичный и блокнот, на котором можно писать.

>> mkdir tautpad
>> cd tautpad

~ Инициализируйте наш модуль go (вы можете сделать это с помощью команды инициализации go mod и добавления к ней суффикса с именем проекта или, если у вас есть учетная запись GitHub и репозиторий для проекта, вы добавляете суффикс URL к команде инициализации go mod.

go mod init tautpad

В качестве альтернативы

go mod init github.com/seyiadel/taut-pad

Мы продолжаем кодировать, открыв наш редактор кода (VScode), и для этого мы используем «код». команда, чтобы открыть наш текущий каталог.

C:UsersSeyi Adeleyetautpad>> code .

Создайте файл main.go в текущем каталоге «tautpad». Откройте файл main.go и напишите;

package main

:::подсказка Зачем упаковывать main в первую строку кода? Это должно сообщить компилятору Go, что пакет является исполняемой программой, а не общей библиотекой.

:::

Теперь мы создадим наш простой веб-сервер и маршрутизатор, используя библиотеку net/http:

import "net/http"

func main(){
  router := http.NewServeMux()

  server := &http.Server(
   Addr: 0.0.0.0:8000
   Handler: router
)
  server.ListenAndServe()
}

:::информация func main(). Это особый тип функции, которая может быть определена в пакете и действует как точка входа для исполняемой программы.

http.NewServeMux() — сопоставляет обработчик с URL-адресом. Подробнее.... р>

:::

На этом этапе нам нужно подключиться к нашей базе данных MongoDB Compass через ее драйвер. Убедитесь, что у вас установлен и запущен MongoDB Community Server.

Если нет, посетите https://www.mongodb.com/try/download/community скачать. Прочтите документацию по установке и получите строку подключения (URI базы данных). Установка поставляется с MongoDB Shell и MongoDB Compass, графическим пользовательским интерфейсом, который упрощает использование.

После этого пришло время подключить наше приложение к установленной базе данных. Во-первых, мы устанавливаем драйверы с помощью команды go get

>> go get go.mongodb.org/mongo-driver/mongo

Теперь в нашем main.go мы подключаемся к базе данных с установленным драйвером и строкой подключения к базе данных (URI базы данных).

Наш файл main.go выглядит так:

package main
import (
    "context"
    "fmt"
    "net/http"

    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    "go.mongodb.org/mongo-driver/mongo/readpref"
)

func main(){
    //Connects to the database with uri generated from the database.
    const uri = "mongodb://localhost:27017"
    client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri))
    if err != nil{
        panic(err)
    }

// Makes sure the database disconnects after function/program runs
    defer func(){
        if err = client.Disconnect(context.TODO()); err != nil{
            panic(err)
        }
    }()
//Check the database is connected and active
    if err := client.Ping(context.TODO(), readpref.Primary()); err !=nil {
        panic(err)
    }
//Database name and the collection to store our data
    collection = client.Database("tautpad").Collection("notes")

    fmt.Println("Database connected and pinged")

    router := http.NewServeMux()

    server := &http.Server{
        Addr : "0.0.0.0:8080",
        Handler: router,
    }

    server.ListenAndServe()
}

:::информация контекст.TODO()

readpref.Primary

:::

Запустите go mod tidy, чтобы включить используемый драйвер в качестве зависимости.

>> go mod tidy

Пришло время CRUD!🎈

Простая заметка имеет заголовок и текст. Давайте создадим модель заметки с этими требованиями, используя тип структуры. Здесь мы начинаем импортировать библиотеки из MongoDB. Первая внешняя библиотека является примитивной, она предназначена для создания идентификатора каждой созданной заметки.

import (
    //internal libraries
    "context"
    "fmt"
    "net/http"

    //external libraries
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    "go.mongodb.org/mongo-driver/mongo/readpref"
)
type Note struct{
  ID primitive.ObjectID `json:"_id" bson:"_id,omitempty"`
  Title string `json:"title bson:"title,omitempty"`
  Body string  `json:"body" bson:"body,omitempty"
}

Мы добились прогресса. Давайте завершим его созданием функций или обработчиков для выполнения CRUD. Мы бы написали обработчик создания и извлечения, что означает запрос POST и GET. Я бы использовал тип switch-case, чтобы сделать обработчики краткими и понятными.

func createAndGetNoteHandler(response http.ResponseWriter, request http.Request){
    response.Header().Set("content-type", "application/json")
    switch request.Method {
    case "POST":
        var note Note

        _ = json.NewDecoder(request.Body).Decode(&note)

        output, err := collection.InsertOne(context.Background(), note)
        if err != nil{
            log.Fatal(err)
        }
        json.NewEncoder(response).Encode(output)


    case "GET":
        var notes []Note

        output, err := collection.Find(context.TODO(), bson.D{})
        if err != nil{
            panic(err)
        }
        err = output.All(context.TODO(), &notes)
        if err != nil{
            panic(err)
        }
        json.NewEncoder(response).Encode(notes)

    }
}

Наконец, мы пишем функции обновления и удаления, чтобы завершить их как CRUD (создать-получить-обновить-удалить). Update принимает запрос PUT, а Delete — запрос DELETE.

func getUpdateDeleteTautPadHandler(response http.ResponseWriter, request *http.Request){
    response.Header().Set("content-type", "application/json")
    var note Note
    ctx := context.TODO()
    switch request.Method{
    case "GET":
        getId := strings.TrimPrefix(request.URL.Path, "/notes/") 
        id, err := primitive.ObjectIDFromHex(getId)
        if err != nil{
            panic(err)
        }
        err = collection.FindOne(ctx, bson.M{"_id": id} ).Decode(&note)
        if err != nil{
                panic(err)
        }
        json.NewEncoder(response).Encode(note)

    case "PUT":
        err := json.NewDecoder(request.Body).Decode(&note)
        if err != nil{
            panic(err)
        }
        getId := strings.TrimPrefix(request.URL.Path, "/notes/")
        id, err := primitive.ObjectIDFromHex(getId)
        if err != nil{
            panic(err)
        }
        result, err := collection.UpdateOne(ctx, bson.M{"_id": id,}, bson.M{"$set": note,})
        if err != nil{
            panic(err)
        }
        json.NewEncoder(response).Encode(result)

    case "DELETE":
        getID := strings.TrimPrefix(request.URL.Path, "/notes/")
        id, err := primitive.ObjectIDFromHex(getID)
        if err != nil{
            panic(err)
        }

        output, err := collection.DeleteOne(ctx, bson.M{"_id": id,})
        if err != nil{
            panic(err)
        }
        json.NewEncoder(response).Encode(output)
    }
}

Мы добавляем созданные нами обработчики к маршрутизаторам, чтобы они соответствовали URL-адресу для выполнения своих операций.

Это основная функция в файле main.go

    router := http.NewServeMux()
    router.Handle("/notes", http.HandlerFunc(tautPadHandler))
    router.Handle("/notes/", http.HandlerFunc(getUpdateDeleteTautPadHandler))

Готово!🎉

Мы успешно написали API-интерфейс Note REST для выполнения операций CRUD с MongoDB в качестве сохраняемости.

Ваш файл main.go должен выглядеть так:

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strings"

    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    "go.mongodb.org/mongo-driver/mongo/readpref"
)

type Note struct {
    /*no space between _id and omitempty else _id generated would be zeros 
    and won't allow another insertion, so as to prompt go drivers for 
    random _id to be generated*/
    Id          primitive.ObjectID `json:"_id" bson:"_id,omitempty"` 
    Title       string `json:"title" bson:"title,omitempty"`
    Description string `json:"description" bson:"description,omiempty"`
}


var collection *mongo.Collection 

const uri = "mongodb://localhost:27017"

func tautPadHandler(response http.ResponseWriter, request *http.Request){
    response.Header().Set("content-type", "application/json")
    switch request.Method {
    case "POST":
        var note Note

        _ =json.NewDecoder(request.Body).Decode(&note)

        output, err := collection.InsertOne(context.Background(), note)
        if err != nil{
            log.Fatal(err)
        }
        json.NewEncoder(response).Encode(output)


    case "GET":
        var notes []Note

        output, err := collection.Find(context.TODO(), bson.D{})
        if err != nil{
            panic(err)
        }
        err = output.All(context.TODO(), &notes)
        if err != nil{
            panic(err)
        }
        json.NewEncoder(response).Encode(notes)

    }
}

func getUpdateDeleteTautPadHandler(response http.ResponseWriter, request *http.Request){
    response.Header().Set("content-type", "application/json")
    var note Note
    ctx := context.TODO()
    switch request.Method{
    case "GET":
        getId := strings.TrimPrefix(request.URL.Path, "/notes/") 
        id, err := primitive.ObjectIDFromHex(getId)
        if err != nil{
            panic(err)
        }
        err = collection.FindOne(ctx, bson.M{"_id": id} ).Decode(&note)
        if err != nil{
                panic(err)
        }
        json.NewEncoder(response).Encode(note)

    case "PUT":
        err := json.NewDecoder(request.Body).Decode(&note)
        if err != nil{
            panic(err)
        }
        getId := strings.TrimPrefix(request.URL.Path, "/notes/")
        id, err := primitive.ObjectIDFromHex(getId)
        if err != nil{
            panic(err)
        }
        result, err := collection.UpdateOne(ctx, bson.M{"_id": id,}, bson.M{"$set": note,})
        if err != nil{
            panic(err)
        }
        json.NewEncoder(response).Encode(result)

    case "DELETE":
        getID := strings.TrimPrefix(request.URL.Path, "/notes/")
        id, err := primitive.ObjectIDFromHex(getID)
        if err != nil{
            panic(err)
        }

        output, err := collection.DeleteOne(ctx, bson.M{"_id": id,})
        if err != nil{
            panic(err)
        }
        json.NewEncoder(response).Encode(output)
    }
}

func main(){

    client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri))
    if err != nil{
        panic(err)
    }

    defer func(){
        if err = client.Disconnect(context.TODO()); err != nil{
            panic(err)
        }
    }()

    if err := client.Ping(context.TODO(), readpref.Primary()); err !=nil {
        panic(err)
    }

    collection = client.Database("tautpad").Collection("notes")

    fmt.Println("Database connected and pinged")


    router := http.NewServeMux()
    router.Handle("/notes", http.HandlerFunc(tautPadHandler))
    router.Handle("/notes/", http.HandlerFunc(getUpdateDeleteTautPadHandler))

    server := &http.Server{
        Addr : "0.0.0.0:8080",
        Handler: router,
    }

    server.ListenAndServe()
}


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE