Appwrite Init: a week-long program dedicated to showcasing and celebrating everything new with Appwrite. The 2024 edition saw the launch of Appwrite 1.5, introducing exciting features such as:
- Local development support for serverless functions.
- A new and better Appwrite command line interface (CLI).
- Improved Appwrite serverless function.
- Go runtime support and SDK
- Mock numbers to test phone authentication.
The addition of Go runtime support and SDK stood out to me in particular. Developers can now build both APIs and serverless functions in Go, eliminating the need to switch contexts between Go and previously supported languages. This is a game changer, as it enables type consistency across APIs and functions, streamlines your deployment process, and significantly enhances the developer experience.
The project repository can be found here.
##Before the Go SDK
Prior to the Go SDK, if you want to build API with Go and Appwrite, you’ll first create a service that talks to Appwrite API:
// 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
}Then use the service to create a handler that exposes the API.
// import dependencies
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) // using the service
if err != nil {
app.errorJSON(ctx, err)
return
}
app.writeJSON(ctx, http.StatusCreated, data)
}
}As seen above, you have to manually call the APIs using an HTTP client, configure headers to include the API key and project ID, switch URLs (when building other endpoints), handle requests, convert responses to JSON, and manage many other overhead tasks.
Now, imagine doing this for multiple requests across different endpoints. It quickly becomes a tedious process, with repetitive code and constant refactoring whenever you need to update the project.
But with Appwrite’s new Go SDK, you can build APIs seamlessly with minimal effort. Let’s take a closer look.
##Getting started
To get started, navigate to the desired directory and run the command below:
mkdir go-appwriteSDK && cd go-appwriteSDKNext, initialize a Go module to manage project dependencies by running the command below:
go mod init go-appwriteSDKFinally, proceed to install the required dependencies with:
go get github.com/appwrite/sdk-for-go github.com/gin-gonic/gin github.com/go-playground/validator/v10 github.com/joho/godotenvgithub.com/appwrite/sdk-for-go is Appwrite Go SDK.
github.com/gin-gonic/gin is a framework for building web applications.
github.com/go-playground/validator/v10 is a library for validating structs and fields.
github.com/joho/godotenv is a library for loading environment variables.
Structuring the application
To do this, create an api, cmd, and data folder in your project directory.
api is for structuring our API-related files.
cmd is for structuring our application entry point.
data is for structuring our application data.
##Setting up Appwrite
Log into our Appwrite console, click the Create project button, input go_appwrite as the name, and then Create.

Create a Database, Collection, and Add Attributes
To do this, first, navigate to the Database tab, click the Create database button, input project as the name, and Create.

Secondly, create a collection for storing the projects by clicking the Create collection button, input project_collection as the name, and then click Create.

Lastly, you need to create attributes to represent the database fields. To do this, navigate to the Attributes tab and create attributes for each of the values shown below:
| Attribute key | Attribute type | Size | Required |
|---|---|---|---|
| name | String | 250 | YES |
| description | String | 5000 | YES |

After creating the attributes, you should see them as shown below:

Create an API key
To securely connect to Appwrite, you need to create an API key. To do this, navigate to the Overview tab, scroll to the Integrate With Your Server section, and click the API Key button.

Next, input api_go as the name, click the Next button, select Database as the required scope, and Create.


##Building the APIs fast with the Appwrite Go SDK
With the project fully set up on Appwrite, you can now use the database with the SDK to build the APIs without any other technical overhead.
Set up Environment Variable
To set up the required environment variables, you need to create a .env file in the root directory and add the snippet below:
API_KEY=<REPLACE WITH API KEY>
PROJECT_ID=<REPLACE WITH PROJECT ID>
DATABASE_ID=<REPLACE WITH DATABASE ID>
COLLECTION_ID=<REPLACE WITH COLLECTION ID>
API_URL=https://cloud.appwrite.io/v1You can get the required key and IDs from your Appwrite console.
Create the API models and helper function
To represent the application data, create a model.go file inside the data folder and add the snippet below:
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 {
Data *ProjectRequest `json:"data,omitempty"`
}The snippet above creates a Project, ProjectRequest, ProjectResponse, and JsonAPIBody struct with the required properties to describe requests and response types.
Finally, create a helpers.go file with a reusable function to load environment variables and return JSON responses inside the api folder.
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()})
}Create the API route entry
Create a route.go file for configuring the API routes inside the api folder and add the snippet below:
package api
import "github.com/gin-gonic/gin"
type Config struct {
Router *gin.Engine
}
func (app *Config) Routes() {
//routes will come here
}The snippet above does the following:
- Imports the required dependency.
- Creates a
Configstruct with aRouterproperty to configure the application methods. - Creates a
Routesfunction that takes in theConfigstruct as a pointer.
Create the API handlers
To create the handlers, first, create a handler.go file inside the api folder and add the snippet below:
package api
import (
"context"
"go-appwriteSDK/data"
"net/http"
"time"
"github.com/appwrite/sdk-for-go/appwrite"
"github.com/appwrite/sdk-for-go/id"
"github.com/gin-gonic/gin"
)
var projectId = GetEnvVariable("PROJECT_ID")
var databaseId = GetEnvVariable("DATABASE_ID")
var collectionId = GetEnvVariable("COLLECTION_ID")
var apiKey = GetEnvVariable("API_KEY")
var endpoint = GetEnvVariable("API_URL")
var client = appwrite.NewClient(
appwrite.WithEndpoint(endpoint),
appwrite.WithProject(projectId),
appwrite.WithKey(apiKey),
)
const appTimeout = time.Second * 10The snippet above imports the required dependencies, uses the GetEnvVariable to load the required enrionment variables, configures Appwrite with the required credentials, and creates a 10 seconds timeout for requests.
Finally, use the Appwrite instance and the timeout to create the handlers.
func (app *Config) createProjectHandler() gin.HandlerFunc {
return func(ctx *gin.Context) {
_, cancel := context.WithTimeout(context.Background(), appTimeout)
databases := appwrite.NewDatabases(client)
var payload data.ProjectRequest
defer cancel()
app.validateJsonBody(ctx, &payload)
doc, err := databases.CreateDocument(databaseId, collectionId, id.Unique(), payload)
if err != nil {
app.errorJSON(ctx, err)
return
}
app.writeJSON(ctx, http.StatusCreated, doc)
}
}
func (app *Config) getProjectHandler() gin.HandlerFunc {
return func(ctx *gin.Context) {
_, cancel := context.WithTimeout(context.Background(), appTimeout)
databases := appwrite.NewDatabases(client)
docId := ctx.Param("projectId")
defer cancel()
response, err := databases.GetDocument(databaseId, collectionId, docId)
if err != nil {
app.errorJSON(ctx, err)
return
}
var document data.Project
err = response.Decode(&document)
if err != nil {
app.errorJSON(ctx, err)
return
}
app.writeJSON(ctx, http.StatusOK, document)
}
}
func (app *Config) updateProjectHandler() gin.HandlerFunc {
return func(ctx *gin.Context) {
_, cancel := context.WithTimeout(context.Background(), appTimeout)
databases := appwrite.NewDatabases(client)
var payload data.ProjectRequest
docId := ctx.Param("projectId")
defer cancel()
app.validateJsonBody(ctx, &payload)
updates := data.ProjectRequest{
Name: payload.Name,
Description: payload.Description,
}
response, err := databases.UpdateDocument(databaseId, collectionId, docId, databases.WithUpdateDocumentData(updates))
if err != nil {
app.errorJSON(ctx, err)
return
}
var document data.Project
err = response.Decode(&document)
if err != nil {
app.errorJSON(ctx, err)
return
}
app.writeJSON(ctx, http.StatusOK, document)
}
}
func (app *Config) deleteProjectHandler() gin.HandlerFunc {
return func(ctx *gin.Context) {
_, cancel := context.WithTimeout(context.Background(), appTimeout)
databases := appwrite.NewDatabases(client)
docId := ctx.Param("projectId")
defer cancel()
response, err := databases.DeleteDocument(databaseId, collectionId, docId)
if err != nil {
app.errorJSON(ctx, err)
return
}
app.writeJSON(ctx, http.StatusOK, response)
}
}Update the API routes to use handlers
With that done, update the routes.go file with the handlers as shown below:
package api
import "github.com/gin-gonic/gin"
type Config struct {
Router *gin.Engine
}
func (app *Config) Routes() {
app.Router.POST("/project", app.createProjectHandler())
app.Router.GET("/project/:projectId", app.getProjectHandler())
app.Router.PUT("/project/:projectId", app.updateProjectHandler())
app.Router.DELETE("/project/:projectId", app.deleteProjectHandler())
}Putting it all together
With the APIs fully set up, you need to create the application entry point. To do this, create a main.go file inside the cmd folder and add the snippet below:
package main
import (
"go-appwriteSDK/api"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
//initialize config
app := api.Config{Router: router}
//routes
app.Routes()
router.Run(":8080")
}The snippet above does the following:
- Imports the required dependencies
- Creates a Gin router using the
Defaultconfiguration - Initialize the
Configstruct by passing in theRouter - Adds the route and run the application on port
:8080
With that done, we can start a development server using the command below:
go run cmd/main.go


Conclusion
This post discusses how to quickly build REST APIs with the Appwrite Go SDK, eliminating the need to manually configure headers for API keys and project IDs, switch URLs for different endpoints, handle requests, and convert responses to JSON.
These resources might be helpful:

