appwrite + go:构建没有技术开销的API
#appwrite #api #go #baas

在SOFWARE开发生命周期(SDLC)的每个阶段,开发人员必须在数据库,授权,部署机制,服务器尺寸,存储管理等方面做出战略决策。这些决策必须进行彻底评估,因为它们可以显着影响开发过程参与构建应用程序。

一个范式开发人员不断拥抱是Backend-as-a-Service (BaaS)。 BAA摘要与SDLC相关的开发开销,并仅关注业务逻辑。它为开发人员提供了服务器端功能,例如用户身份验证,数据库管理,云存储等。

在这篇文章中,我们将通过在GO中构建项目管理API来探索AppWrite作为BAA的利用。 API将提供创建,阅读,更新和删除项目的功能。可以找到项目存储库here

什么是AppWrite?

AppWrite是一个开源后端,作为一个服务平台,可为构建Web,移动和后端服务提供一组API和SDK。以下是在任何应用程序中使用AppWrite的一些好处:

  • 提供可扩展且稳健的数据库
  • 实时功能
  • 支持无服务器功能
  • 安全证书和加密
  • 身份验证和授权机制

先决条件

要与本教程一起,需要以下内容:

  • 对GO的基本理解
  • AppWrite帐户。注册是free

入门

要开始,我们需要导航到所需的目录并运行以下命令:

cargo new go-appwrite && cd go-appwrite

此命令创建一个名为go-appwrite的GO项目,并导航到项目目录。

接下来,我们需要通过运行以下命令来初始化一个GO模块来管理项目依赖项:

go mod init go-appwrite

此命令将创建一个用于跟踪项目依赖项的go.mod文件。

最后,我们继续使用:
安装所需的依赖项

go get github.com/gin-gonic/gin github.com/go-playground/validator/v10 github.com/joho/godotenv

github.com/gin-gonic/gin是构建Web应用程序的框架。

github.com/go-playground/validator/v10是用于验证结构和字段的库。

github.com/joho/godotenv是一个用于加载环境变量的库

构建我们的应用程序

必须拥有一个良好的项目结构,因为它使代码库可维护和无缝供任何人阅读或管理。为此,我们必须在项目目录中创建一个apicmddata文件夹。

project directory

api用于构建我们的API相关文件

cmd用于构建我们的应用程序入口点

data用于构建我们的应用程序数据

设置AppWrite

要开始,我们需要登录到我们的appwrite console,单击创建project 按钮,输入go_appwrite作为名称,然后 create

Create project

创建数据库,集合并添加属性

appwrite船只可以在构建项目管理API方面使用可扩展且可靠的数据库。为此,首先,导航到数据库选项卡,单击创建数据库按钮,输入project作为名称,然后 create 。。 P>

Create database

其次,我们需要创建一个用于存储项目的集合。为此,请单击创建集合按钮,输入project_collection作为名称,然后单击创建

Create collection

最后,我们需要创建属性来表示我们的数据库字段。为此,我们需要导航到属性选项卡,并为下面显示的每个值创建属性:

属性键 属性类型 大小 必需
名称 字符串 250
描述 字符串 5000

Create attribute
create

创建属性后,我们看到它们如下所示:

List of attributes

创建一个API键

要安全地连接到AppWrite,我们需要创建一个API密钥。为此,我们需要导航到概述选项卡,滚动到与服务器部分集成,然后单击 api键按钮。

Create API key

接下来,输入api_go作为名称,单击下一个按钮,选择数据库作为所需范围,然后 create

input  name create
Set permission

利用AppWrite在GO中构建项目管理API

通过在AppWrite上完全设置我们的项目,我们现在可以使用数据库而无需旋转任何服务器或管理任何其他技术开销。

设置环境变量

要安全地连接到我们的AppWrite Provisioned Server,AppWrite提供了一个端点和一组唯一ID,我们可以使用这些ID来执行所有必需的操作。要设置所需的环境变量,我们需要在根目录中创建一个.env文件,然后添加下面的摘要:

API_KEY=<REPLACE WITH API KEY>
PROJECT_ID=<REPLACE WITH PROJECT ID>
DATABASE_ID=<REPLACE WITH DATABASE ID>
COLLECTION_ID=<REPLACE WITH COLLECTION ID>

我们可以从AppWrite控制台中获取所需的API键和ID,如下所示:

API key

Project ID

Database and Collection ID

创建API模型

接下来,我们需要创建模型来表示我们的应用程序数据。为此,我们需要在data文件夹中创建一个model.go文件,然后添加下面的摘要:

package data

type Project struct {
    Id          string `json:"$id,omitempty"`
    Name        string `json:"name,omitempty"`
    Description string `json:"description,omitempty"`
}

type ProjectRequest struct {
    Name        string `json:"name,omitempty"`
    Description string `json:"description,omitempty"`
}

type ProjectResponse struct {
    Id           string `json:"$id,omitempty"`
    CollectionId string `json:"$collectionId,omitempty"`
}

type JsonAPIBody struct {
    DocumentId string          `json:"documentId,omitempty"`
    Data       *ProjectRequest `json:"data,omitempty"`
}

上面的摘要创建了ProjectProjectRequestProjectResponseJsonAPIBody结构,并具有所需属性来描述请求和响应类型。

创建API路由

使用模型完全设置,我们需要导航到api文件夹,并创建一个用于配置API路由的route.go文件,并在下面添加摘要:

package api

import "github.com/gin-gonic/gin"

type Config struct {
    Router *gin.Engine
}

func (app *Config) Routes() {
    //routes will come here
}

上面的摘要执行以下操作:

  • 导入所需的依赖项
  • 使用Router属性创建一个Config结构,以配置应用程序方法
  • 创建一个Routes函数,该函数将Config struct作为指针

创建API帮助者

通过完全设置应用程序模型,我们现在使用它们来创建我们的应用程序逻辑。为此,我们需要在API文件夹中创建一个helper.go文件,然后添加下面的摘要:

package api

import (
    "log"
    "net/http"
    "os"
    "github.com/gin-gonic/gin"
    "github.com/go-playground/validator/v10"
    "github.com/joho/godotenv"
)

type jsonResponse struct {
    Status  int    `json:"status"`
    Message string `json:"message"`
    Data    any    `json:"data"`
}

func GetEnvVariable(key string) string {
    err := godotenv.Load()
    if err != nil {
        log.Fatal("Error loading .env file")
    }
    return os.Getenv(key)
}

func (app *Config) validateJsonBody(c *gin.Context, data any) error {
    var validate = validator.New()
    //validate the request body
    if err := c.BindJSON(&data); err != nil {
        return err
    }
    //validate with the validator library
    if err := validate.Struct(&data); err != nil {
        return err
    }
    return nil
}

func (app *Config) writeJSON(c *gin.Context, status int, data any) {
    c.JSON(status, jsonResponse{Status: status, Message: "success", Data: data})
}

func (app *Config) errorJSON(c *gin.Context, err error, status ...int) {
    statusCode := http.StatusBadRequest
    if len(status) > 0 {
        statusCode = status[0]
    }
    c.JSON(statusCode, jsonResponse{Status: statusCode, Message: err.Error()})
}

上面的摘要执行以下操作:

  • 导入所需的依赖项
  • 创建一个jsonResponse结构来描述API响应
  • 创建一个使用godotenv软件包加载和获取环境变量的GetEnvVariable函数
  • 创建一个validateBody函数,该函数将Config struct作为指针作为指针并返回error。在函数内部,我们验证请求data的格式正确,还使用验证器库进行验证并检查所需的字段
  • 创建一个writeJSON函数,该函数将Config struct作为指针作为指针,并使用jsonResponse struct构造API响应。
  • 创建一个errorJSON函数,该函数将Config struct作为指针作为指针,并在存在错误时使用jsonResponse struct构造API响应

创建API服务

通过完全设置应用程序模型,我们现在使用它们来创建我们的应用程序逻辑。为此,我们需要创建一个service.go文件,并通过以下内容进行更新:

首先,我们需要导入所需的依赖项,创建环境变量并创建用于创建项目的函数。

package api
import (
    "bytes"
    "encoding/json"
    "fmt"
    "go-appwrite/data"
    "io/ioutil"
    "net/http"
)
//get details from environment variable
var projectId = GetEnvVariable("PROJECT_ID")
var databaseId = GetEnvVariable("DATABASE_ID")
var collectionId = GetEnvVariable("COLLECTION_ID")
var apiKey = GetEnvVariable("PROJECT_ID")

func (app *Config) createProject(newProject *data.ProjectRequest) (*data.ProjectResponse, error) {
    url := fmt.Sprintf("https://cloud.appwrite.io/v1/databases/%s/collections/%s/documents", databaseId, collectionId)

    createdProject := data.ProjectResponse{}
    jsonData := data.JsonAPIBody{
        DocumentId: "unique()",
        Data:       newProject,
    }
    postBody, _ := json.Marshal(jsonData)
    bodyData := bytes.NewBuffer(postBody)

    //making the request
    client := &http.Client{}
    req, _ := http.NewRequest("POST", url, bodyData)
    req.Header.Add("Content-Type", "application/json")
    req.Header.Add("X-Appwrite-Key", apiKey)
    req.Header.Add("X-Appwrite-Project", projectId)
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    err = json.Unmarshal([]byte(body), &createdProject)
    if err != nil {
        return nil, err
    }
    return &createdProject, nil
}

上面的摘要执行以下操作:

  • 导入所需的依赖项
  • 创建所需的环境变量
  • 创建一个createProject函数,该函数将Config struct作为指针,并返回创建的项目详细信息或错误。这些函数还通过包括所需的环境变量,添加所需的标题并提出请求。

ps 创建项目时指定的unique()标签告诉appwrite以自动化项目ID。

其次,我们需要添加一个使用与createProject功能相似的逻辑来获取项目详细信息的getProject函数。

//import goes here

func (app *Config) createProject(newProject *data.ProjectRequest) (*data.ProjectResponse, error) {
//createProject code goes here
}

func (app *Config) getProject(documentId string) (*data.Project, error) {
    url := fmt.Sprintf("https://cloud.appwrite.io/v1/databases/%s/collections/%s/documents/%s", databaseId, collectionId, documentId)

    projectDetail := data.Project{}

    //making the request
    client := &http.Client{}
    req, _ := http.NewRequest("GET", url, nil)
    req.Header.Add("Content-Type", "application/json")
    req.Header.Add("X-Appwrite-Key", apiKey)
    req.Header.Add("X-Appwrite-Project", projectId)

    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    err = json.Unmarshal([]byte(body), &projectDetail)
    if err != nil {
        return nil, err
    }
    return &projectDetail, nil
}

第三,我们需要添加一个updateProject功能,该功能使用与createProject功能相似的逻辑来更新项目的详细信息。

//import goes here

func (app *Config) createProject(newProject *data.ProjectRequest) (*data.ProjectResponse, error) {
//createProject code goes here
}

func (app *Config) getProject(documentId string) (*data.Project, error) {
    //getProject code goes here
}

func (app *Config) updateProject(updatedProject *data.ProjectRequest, documentId string) (*data.ProjectResponse, error) {
    url := fmt.Sprintf("https://cloud.appwrite.io/v1/databases/%s/collections/%s/documents/%s", databaseId, collectionId, documentId)

    updates := data.ProjectResponse{}

    jsonData := data.JsonAPIBody{
        Data: updatedProject,
    }
    postBody, _ := json.Marshal(jsonData)
    bodyData := bytes.NewBuffer(postBody)

    //making the request
    client := &http.Client{}
    req, _ := http.NewRequest("PATCH", url, bodyData)
    req.Header.Add("Content-Type", "application/json")
    req.Header.Add("X-Appwrite-Key", apiKey)
    req.Header.Add("X-Appwrite-Project", projectId)
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    err = json.Unmarshal([]byte(body), &updates)
    if err != nil {
        return nil, err
    }
    return &updates, nil
}

最后,我们需要添加一个使用与createProject函数相似的逻辑来删除项目详细信息的deleteProject函数。

//import goes here

func (app *Config) createProject(newProject *data.ProjectRequest) (*data.ProjectResponse, error) {
//createProject code goes here
}

func (app *Config) getProject(documentId string) (*data.Project, error) {
    //getProject code goes here
}

func (app *Config) updateProject(updatedProject *data.ProjectRequest, documentId string) (*data.ProjectResponse, error) {
    //updateProject code goes here
}

func (app *Config) deleteProject(documentId string) (string, error) {
    url := fmt.Sprintf("https://cloud.appwrite.io/v1/databases/%s/collections/%s/documents/%s", databaseId, collectionId, documentId)

    //making the request
    client := &http.Client{}
    req, _ := http.NewRequest("DELETE", url, nil)
    req.Header.Add("Content-Type", "application/json")
    req.Header.Add("X-Appwrite-Key", apiKey)
    req.Header.Add("X-Appwrite-Project", projectId)

    resp, err := client.Do(req)
    if err != nil {
        return "", err
    }
    _, err = ioutil.ReadAll(resp.Body)
    if err != nil {
        return "", err
    }
    return documentId, nil
}

创建API处理程序

完成此操作,我们可以使用服务来创建API处理程序。为此,我们需要在api文件夹中创建一个handler.go文件,然后添加下面的摘要:

package api

import (
    "context"
    "fmt"
    "go-appwrite/data"
    "net/http"
    "time"
    "github.com/gin-gonic/gin"
)

const appTimeout = time.Second * 10

func (app *Config) createdProjectHandler() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        _, cancel := context.WithTimeout(context.Background(), appTimeout)
        var payload data.ProjectRequest
        defer cancel()

        app.validateJsonBody(ctx, &payload)

        newProject := data.ProjectRequest{
            Name:        payload.Name,
            Description: payload.Description,
        }

        data, err := app.createProject(&newProject)

        if err != nil {
            app.errorJSON(ctx, err)
            return
        }

        app.writeJSON(ctx, http.StatusCreated, data)
    }
}

func (app *Config) getProjectHandler() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        _, cancel := context.WithTimeout(context.Background(), appTimeout)
        projectId := ctx.Param("projectId")
        defer cancel()

        data, err := app.getProject(projectId)

        if err != nil {
            app.errorJSON(ctx, err)
            return
        }

        app.writeJSON(ctx, http.StatusOK, data)
    }
}

func (app *Config) updateProjectHandler() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        _, cancel := context.WithTimeout(context.Background(), appTimeout)
        projectId := ctx.Param("projectId")
        var payload data.ProjectRequest
        defer cancel()

        app.validateJsonBody(ctx, &payload)
        newProject := data.ProjectRequest{
            Name:        payload.Name,
            Description: payload.Description,
        }

        data, err := app.updateProject(&newProject, projectId)
        if err != nil {
            app.errorJSON(ctx, err)
            return
        }

        app.writeJSON(ctx, http.StatusOK, data)
    }
}

func (app *Config) deleteProjectHandler() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        _, cancel := context.WithTimeout(context.Background(), appTimeout)
        projectId := ctx.Param("projectId")
        defer cancel()

        data, err := app.deleteProject(projectId)
        if err != nil {
            app.errorJSON(ctx, err)
            return
        }

        app.writeJSON(ctx, http.StatusAccepted, fmt.Sprintf("Project with ID: %s deleted successfully!!", data))
    }
}

上面的摘要执行以下操作:

  • 导入所需的依赖项
  • 创建一个createdProjectHandlergetProjectHandlerupdateProjectHandlerdeleteProjectHandler函数,该功能返回杜松子酒处理程序,并将Config结构作为指针接收。在返回的处理程序中,我们定义了API超时,使用了助手功能,并较早创建的服务执行相应的操作。

更新使用处理程序的API路由

完成此操作,我们现在可以使用处理程序更新routes.go文件,如下所示:

package api
import "github.com/gin-gonic/gin"

type Config struct {
    Router *gin.Engine
}

func (app *Config) Routes() {
    app.Router.POST("/project", app.createdProjectHandler())
    app.Router.GET("/project/:projectId", app.getProjectHandler())
    app.Router.PATCH("/project/:projectId", app.updateProjectHandler())
    app.Router.DELETE("/project/:projectId", app.deleteProjectHandler())
}

将它们全部放在一起

通过完全设置API,我们需要创建应用程序输入点。为此,我们需要在cmd文件夹中创建一个main.go文件,然后添加下面的摘要:

package main

import (
    "go-appwrite/api"
    "github.com/gin-gonic/gin"
)

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

    //initialize config
    app := api.Config{Router: router}

    //routes
    app.Routes()

    router.Run(":8080")
}

上面的摘要执行以下操作:

  • 导入所需的依赖项
  • 使用Default配置创建杜松子路由器
  • 通过传递Router初始化Config结构
  • 添加路线并在端口:8080上运行应用程序

完成此操作,我们可以使用以下命令启动开发服务器:

go run cmd/main.go

create
update

Get details
Delete

我们还可以通过在AppWrite上检查集合来确认项目管理数据。

Detail

结论

这篇文章讨论了什么是AppWrite,并提供了详细的分步指南,用于在GO中构建项目管理API。

这些资源也可能有帮助: