mcpField
A
appwrite-go

appwrite-go

github.com/appwrite开发28902 次安装1 周前更新
代码仓库

关于 appwrite-go

appwrite-go 是在 Go 应用中使用 Appwrite Go SDK 的技能,涵盖常见服务端操作。内容涉及当前数据库 API、权限、身份验证、存储和函数。

主要功能

  1. 01管理用户和团队
  2. 02使用 TablesDB 创建表和行
  3. 03通过查询辅助方法筛选数据
  4. 04使用存储 API 上传文件
  5. 05执行无服务器函数
  6. 06配置权限和 SSR 身份验证

适用场景

  1. 01为 Go 服务添加 Appwrite 数据操作
  2. 02实现用户和团队管理
  3. 03按权限存储文件
  4. 04处理服务端会话和 OAuth

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/appwrite/skills --skill appwrite-go

安装后在 Claude Code 中输入/appwrite-go确认已加载。

详细介绍

Appwrite Go SDK

安装

go get github.com/appwrite/sdk-for-go

设置客户端

import (
    "os"

    "github.com/appwrite/sdk-for-go/client"
    "github.com/appwrite/sdk-for-go/id"
    "github.com/appwrite/sdk-for-go/users"
    "github.com/appwrite/sdk-for-go/tablesdb"
    "github.com/appwrite/sdk-for-go/storage"
)

clt := client.New(
    client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
    client.WithProject(os.Getenv("APPWRITE_PROJECT_ID")),
    client.WithKey(os.Getenv("APPWRITE_API_KEY")),
)

代码示例

用户管理
service := users.New(clt)

// Create user
user, err := service.Create(
    id.Unique(),
    "[email protected]",
    "password123",
    users.WithCreateName("User Name"),
)

// List users
list, err := service.List()

// Get user
fetched, err := service.Get("[USER_ID]")

// Delete user
_, err = service.Delete("[USER_ID]")
数据库操作

注意: 所有新代码都使用 TablesDB(而非已弃用的 Databases 类)。只有在现有代码库已依赖 Databases,或用户明确要求时,才使用 Databases。

提示: 在可用时,优先使用显式的函数选项参数(例如 tablesdb.WithUpdateRowData(...)),而不是不带名称的位置参数。只有在现有代码库已采用纯位置参数的方式,或用户明确要求时,才使用这种方式。

service := tablesdb.New(clt)

// Create database
db, err := service.Create(id.Unique(), "My Database")

// Create row
doc, err := service.CreateRow(
    "[DATABASE_ID]",
    "[TABLE_ID]",
    id.Unique(),
    map[string]interface{}{"title": "Hello World"},
)

// List rows
results, err := service.ListRows("[DATABASE_ID]", "[TABLE_ID]")

// Get row
row, err := service.GetRow("[DATABASE_ID]", "[TABLE_ID]", "[ROW_ID]")

// Update row
_, err = service.UpdateRow(
    "[DATABASE_ID]",
    "[TABLE_ID]",
    "[ROW_ID]",
    tablesdb.WithUpdateRowData(map[string]interface{}{"title": "Updated"}),
)

// Delete row
_, err = service.DeleteRow("[DATABASE_ID]", "[TABLE_ID]", "[ROW_ID]")
字符串列类型

注意: 旧版 string 类型已弃用。所有新列都使用显式列类型。

类型最大字符数索引存储
varchar16,383完整索引(如果大小 ≤ 768)行内
text16,383仅前缀页外
mediumtext4,194,303仅前缀页外
longtext1,073,741,823仅前缀页外
  • varchar 以内联方式存储,并计入 64 KB 的行大小限制。短小且需要索引的字段(如名称、slug 或标识符)优先使用此类型。
  • text、mediumtext 和 longtext 以页外方式存储(行内仅保留一个 20 字节的指针),因此不会占用行大小额度。这些类型无需指定 size。
// Create table with explicit string column types
_, err = service.CreateTable(
    "[DATABASE_ID]",
    id.Unique(),
    "articles",
    tablesdb.WithCreateTableColumns([]map[string]interface{}{
        {"key": "title",    "type": "varchar",    "size": 255, "required": true},
        {"key": "summary",  "type": "text",                    "required": false},
        {"key": "body",     "type": "mediumtext",              "required": false},
        {"key": "raw_data", "type": "longtext",                "required": false},
    }),
)
查询方法
import "github.com/appwrite/sdk-for-go/query"

// Filtering
query.Equal("field", "value")             // == (or pass slice for IN)
query.NotEqual("field", "value")          // !=
query.LessThan("field", 100)             // <
query.LessThanEqual("field", 100)        // <=
query.GreaterThan("field", 100)          // >
query.GreaterThanEqual("field", 100)     // >=
query.Between("field", 1, 100)           // 1 <= field <= 100
query.IsNull("field")                    // is null
query.IsNotNull("field")                 // is not null
query.StartsWith("field", "prefix")      // starts with
query.EndsWith("field", "suffix")        // ends with
query.Contains("field", "sub")           // contains
query.Search("field", "keywords")        // full-text search (requires index)

// Sorting
query.OrderAsc("field")
query.OrderDesc("field")

// Pagination
query.Limit(25)                          // max rows (default 25, max 100)
query.Offset(0)                          // skip N rows
query.CursorAfter("[ROW_ID]")            // cursor pagination (preferred)
query.CursorBefore("[ROW_ID]")

// Selection & Logic
query.Select([]string{"field1", "field2"})
query.Or([]string{query.Equal("a", 1), query.Equal("b", 2)})    // OR
query.And([]string{query.GreaterThan("age", 18), query.LessThan("age", 65)})  // AND (default)
query.Relationship("comments", []string{query.Equal("approved", true), query.Limit(25)})  // related documents

// Aggregations (optional alias)
query.Count("*", "total")
query.Sum("amount", "revenue")
query.Avg("score", "avgScore")
query.Min("price", "minPrice")
query.Max("price", "maxPrice")

// Grouping
query.GroupBy([]interface{}{"status", "year"})
query.Having([]string{query.GreaterThan("total", 1)})
query.Distinct()

// Joins (table id, left key, right key, operator, alias)
query.Join("orders", "$id", "customerId", "=", "")
query.LeftJoin("orders", "$id", "customerId", "=", "ord")
query.LeftJoinOn("orders", "ord", []string{
    query.On("$id", "customerId", ""),
    query.Equal("ord.status", "paid"),
})

// Fluent builder. Pass Build() into list methods.
queries := query.NewBuilder().
    Equal("status", "published").
    LeftJoin("orders", "$id", "customerId", "=", "ord").
    Count("*", "total").
    GroupBy([]interface{}{"status"}).
    Limit(25).
    Build()

_, _ = service.ListRows("[DATABASE_ID]", "[TABLE_ID]", service.WithListRowsQueries(queries))
文件存储
import "github.com/appwrite/sdk-for-go/file"

service := storage.New(clt)

// Upload file
f, err := service.CreateFile(
    "[BUCKET_ID]",
    "[FILE_ID]",
    file.NewInputFile("/path/to/file.png", "file.png"),
)

// List files
files, err := service.ListFiles("[BUCKET_ID]")

// Delete file
_, err = service.DeleteFile("[BUCKET_ID]", "[FILE_ID]")
InputFile 工厂方法
import "github.com/appwrite/sdk-for-go/file"

file.NewInputFile("/path/to/file.png", "file.png")          // from filesystem path
file.NewInputFileFromReader(reader, "file.png", size)        // from io.Reader (size required)
file.NewInputFileFromBytes(data, "file.png")                 // from []byte
团队
import "github.com/appwrite/sdk-for-go/teams"

svc := teams.New(clt)

// Create team
team, err := svc.Create(id.Unique(), "Engineering")

// List teams
list, err := svc.List()

// Create membership (invite user by email)
membership, err := svc.CreateMembership(
    "[TEAM_ID]",
    []string{"editor"},
    teams.WithCreateMembershipEmail("[email protected]"),
)

// List memberships
members, err := svc.ListMemberships("[TEAM_ID]")

// Update membership roles
_, err = svc.UpdateMembership("[TEAM_ID]", "[MEMBERSHIP_ID]", []string{"admin"})

// Delete team
_, err = svc.Delete("[TEAM_ID]")

基于角色的访问控制: 设置权限时,使用 role.Team("[TEAM_ID]") 指定所有团队成员,或使用 role.Team("[TEAM_ID]", "editor") 指定具有特定团队角色的成员。

无服务器函数
import "github.com/appwrite/sdk-for-go/functions"

svc := functions.New(clt)

// Execute function
execution, err := svc.CreateExecution(
    "[FUNCTION_ID]",
    functions.WithCreateExecutionBody(`{"key": "value"}`),
)

// List executions
executions, err := svc.ListExecutions("[FUNCTION_ID]")
编写函数处理程序(Go 运行时)
// src/main.go — Appwrite Function entry point
package handler

import (
    "github.com/open-runtimes/types-for-go/v4/openruntimes"
)

func Main(context openruntimes.Context) openruntimes.Response {
    // context.Req.Body        — raw body (string)
    // context.Req.BodyJson    — parsed JSON (map[string]interface{})
    // context.Req.Headers     — headers (map[string]string)
    // context.Req.Method      — HTTP method
    // context.Req.Path        — URL path
    // context.Req.Query       — query params (map[string]string)

    context.Log("Processing: " + context.Req.Method + " " + context.Req.Path)

    if context.Req.Method == "GET" {
        return context.Res.Json(map[string]interface{}{"message": "Hello!"})
    }

    return context.Res.Json(map[string]interface{}{"success": true})
    // context.Res.Text("Hello")                  // plain text
    // context.Res.Empty()                         // 204
    // context.Res.Redirect("https://...")          // 302
}
服务端渲染(SSR)身份验证

使用 Go 框架(net/http、Gin、Echo、Chi 等)的 SSR 应用通过服务端 SDK处理身份验证。需要两个客户端:

  • 管理客户端 — 使用 API 密钥创建会话,绕过速率限制(可复用的单例)
  • 会话客户端 — 使用会话 Cookie 代表用户执行操作(每个请求创建一个,切勿共享)
import (
    "github.com/appwrite/sdk-for-go/client"
    "github.com/appwrite/sdk-for-go/account"
)

// Admin client (reusable)
adminClient := client.New(
    client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
    client.WithProject(os.Getenv("APPWRITE_PROJECT_ID")),
    client.WithKey(os.Getenv("APPWRITE_API_KEY")),
)

// Session client (create per-request)
sessionClient := client.New(
    client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
    client.WithProject(os.Getenv("APPWRITE_PROJECT_ID")),
)

cookie, err := r.Cookie("a_session_[PROJECT_ID]")
if err == nil {
    sessionClient.SetSession(cookie.Value)
}
电子邮件/密码登录
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
    svc := account.New(adminClient)
    session, err := svc.CreateEmailPasswordSession(r.FormValue("email"), r.FormValue("password"))
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    // Cookie name must be a_session_<PROJECT_ID>
    http.SetCookie(w, &http.Cookie{
        Name:     "a_session_[PROJECT_ID]",
        Value:    session.Secret,
        HttpOnly: true,
        Secure:   true,
        SameSite: http.SameSiteStrictMode,
        Path:     "/",
    })

    w.Header().Set("Content-Type", "application/json")
    w.Write([]byte(`{"success": true}`))
})
已验证身份的请求
http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
    cookie, err := r.Cookie("a_session_[PROJECT_ID]")
    if err != nil {
        http.Error(w, "Unauthorized", http.StatusUnauthorized)
        return
    }

    sessionClient := client.New(
        client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        client.WithProject(os.Getenv("APPWRITE_PROJECT_ID")),
        client.WithSession(cookie.Value),
    )

    svc := account.New(sessionClient)
    user, err := svc.Get()
    // Marshal user to JSON and write response
})
OAuth2 SSR 流程
// Step 1: Redirect to OAuth provider
http.HandleFunc("/oauth", func(w http.ResponseWriter, r *http.Request) {
    svc := account.New(adminClient)
    redirectURL, err := svc.CreateOAuth2Token(
        "github",
        account.WithCreateOAuth2TokenSuccess("https://example.com/oauth/success"),
        account.WithCreateOAuth2TokenFailure("https://example.com/oauth/failure"),
    )
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    http.Redirect(w, r, redirectURL, http.StatusFound)
})

// Step 2: Handle callback — exchange token for session
http.HandleFunc("/oauth/success", func(w http.ResponseWriter, r *http.Request) {
    svc := account.New(adminClient)
    session, err := svc.CreateSession(r.URL.Query().Get("userId"), r.URL.Query().Get("secret"))
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    http.SetCookie(w, &http.Cookie{
        Name: "a_session_[PROJECT_ID]", Value: session.Secret,
        HttpOnly: true, Secure: true, SameSite: http.SameSiteStrictMode, Path: "/",
    })
    w.Write([]byte(`{"success": true}`))
})

Cookie 安全: 始终使用 HttpOnly、Secure 和 SameSiteStrictMode 来防止 XSS。Cookie 名称必须为 a_session_<PROJECT_ID>。

转发用户代理: 调用 sessionClient.SetForwardedUserAgent(r.Header.Get("User-Agent")),以记录最终用户的浏览器信息,便于调试和保障安全。

错误处理

import "github.com/appwrite/sdk-for-go/apperr"

doc, err := service.GetRow("[DATABASE_ID]", "[TABLE_ID]", "[ROW_ID]")
if err != nil {
    var appErr *apperr.AppwriteException
    if errors.As(err, &appErr) {
        fmt.Println(appErr.Message)    // human-readable message
        fmt.Println(appErr.Code)       // HTTP status code (int)
        fmt.Println(appErr.Type)       // error type (e.g. "document_not_found")
    }
}

常见错误代码:

代码含义
401未授权 — 缺少或无效的会话/API 密钥
403禁止访问 — 权限不足
404未找到 — 资源不存在
409冲突 — ID 重复或违反唯一性约束
429请求过多 — 请求次数超出限制

权限与角色(重要)

Appwrite 使用权限字符串控制对资源的访问。每项权限将一个操作(read、update、delete、create,或授予创建、更新和删除权限的 write)与一个角色目标配对。默认情况下,除非在行/文件级别明确设置权限,或从表/存储桶设置继承权限,否则任何用户都无法访问。权限是使用 permission 和 role 助手构建的字符串数组。

import (
    "github.com/appwrite/sdk-for-go/permission"
    "github.com/appwrite/sdk-for-go/role"
)
带权限的数据库行
doc, err := service.CreateRow(
    "[DATABASE_ID]",
    "[TABLE_ID]",
    "[ROW_ID]",
    map[string]interface{}{"title": "Hello World"},
    tablesdb.WithCreateRowPermissions([]string{
        permission.Read(role.User("[USER_ID]")),     // specific user can read
        permission.Update(role.User("[USER_ID]")),   // specific user can update
        permission.Read(role.Team("[TEAM_ID]")),     // all team members can read
        permission.Read(role.Any()),                 // anyone (including guests) can read
    }),
)
带权限的文件上传
f, err := service.CreateFile(
    "[BUCKET_ID]",
    "[FILE_ID]",
    file.NewInputFile("/path/to/file.png", "file.png"),
    storage.WithCreateFilePermissions([]string{
        permission.Read(role.Any()),
        permission.Update(role.User("[USER_ID]")),
        permission.Delete(role.User("[USER_ID]")),
    }),
)

何时设置权限: 需要对每个资源进行访问控制时,设置行级/文件级权限。如果表中的所有行都遵循相同的规则,请在表/存储桶级别配置权限,并将行权限留空。

常见错误:

  • 忘记设置权限 — 所有用户(包括创建者)都将无法访问该资源
  • 对 write/update/delete 使用 role.Any() — 允许任何用户(包括未经身份验证的访客)修改或删除该资源
  • 对敏感数据使用 permission.Read(role.Any()) — 会使该资源可被公开读取

评论

支持 Markdown