关于 appwrite-go
appwrite-go 是在 Go 应用中使用 Appwrite Go SDK 的技能,涵盖常见服务端操作。内容涉及当前数据库 API、权限、身份验证、存储和函数。
主要功能
- 01管理用户和团队
- 02使用 TablesDB 创建表和行
- 03通过查询辅助方法筛选数据
- 04使用存储 API 上传文件
- 05执行无服务器函数
- 06配置权限和 SSR 身份验证
适用场景
- 01为 Go 服务添加 Appwrite 数据操作
- 02实现用户和团队管理
- 03按权限存储文件
- 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类型已弃用。所有新列都使用显式列类型。
| 类型 | 最大字符数 | 索引 | 存储 |
|---|---|---|---|
varchar | 16,383 | 完整索引(如果大小 ≤ 768) | 行内 |
text | 16,383 | 仅前缀 | 页外 |
mediumtext | 4,194,303 | 仅前缀 | 页外 |
longtext | 1,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())— 会使该资源可被公开读取