Xata + Go: A getting started guide.
Tags: xata, go, restapi
Published: 2024-02-05 09:14:35

[Xata](https://xata.io/?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog) is a serverless data platform for building modern and robust applications. Built on top of PostgreSQL, Xata provides a unified REST API for efficient data management. Setting itself apart from other data platforms, Xata introduces unique functionalities that significantly streamline the developer workflow. Here are some key benefits of integrating Xata into any application:

-   **Robust file management**: Xata provides APIs and SDKs to manage and securely upload images, documents, and more, directly to a database record.
-   **Multiple environments support and workflow**: With Xata, creating isolated production environments for testing, staging, or feature releases is seamless.
-   **Fast search support**: Xata automatically indexes uploaded data, facilitating fast and efficient data searches across tables and branches.
-   **AI support**: Xata offers vector embedding and AI solutions that empower the development of intelligent applications.

To experience the capabilities of Xata, we will build a project management API in Go. This API will offer features for creating, reading, updating, and deleting (CRUD) projects. The project repository can be found [here](https://github.com/Mr-Malomz/go-xata.git).

## Prerequisites

To follow along with this tutorial, the following are needed:

-   Basic understanding of Go
-   Xata account. Signup is [free](https://app.xata.io/signin?mode=signup?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog)
-   [Postman](https://www.postman.com/downloads/) or any API testing application of your choice

## Getting started

To get started, we need to navigate to the desired directory and run the command below:

```bash
mkdir go-xata && cd go-xata
```

This command creates a Go project called `go-xata` and navigates into the project directory.

Next, we need to initialize a Go module to manage project dependencies by running the command below:

```bash
go mod init go-xata
```

This command will create a `go.mod` file for tracking the project dependencies.

Finally, we proceed to install the required dependencies with:

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

`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 variable.

**Structuring the application**

It is essential to have a good project structure as it makes the codebase maintainable and seamless for anyone to read or manage.

To do this, we must create an `api`, `cmd`, and `data` folder in our 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.

## Setup the database on Xata

To get started, log into the [Xata workspace](https://app.xata.io/workspaces) and create a `project` database. Inside the `project` database, create a `Project` table and add columns as shown below:

| **Column type** | **Column name** |
| --------------- | --------------- |
| String          | name            |
| Text            | description     |
| String          | status          |

![Create database](https://paper-attachments.dropboxusercontent.com/s_E1CBF4A45089EFC3D9C461B80BB9F97D882823FECDCA010DD86FEAC6EC26A79C_1706440285877_Screenshot+2024-01-28+at+12.11.03.png)
![Add table](https://paper-attachments.dropboxusercontent.com/s_E1CBF4A45089EFC3D9C461B80BB9F97D882823FECDCA010DD86FEAC6EC26A79C_1706442245485_Screenshot+2024-01-20+at+22.09.07.png)

![Add field](https://paper-attachments.dropboxusercontent.com/s_E1CBF4A45089EFC3D9C461B80BB9F97D882823FECDCA010DD86FEAC6EC26A79C_1706442310277_Screenshot+2024-01-20+at+22.35.14.png)
![Add field](https://paper-attachments.dropboxusercontent.com/s_0A5EE05DD33973CD0E04539FE322F6D7F627FC48B3BBD5141D2B41A147A367E1_1706521726017_Screenshot+2024-01-20+at+22.39.31.png)

Inside a table, Xata automatically adds an `id`, `xata.createdAt`, `xata.updatedAt`, and `xata.version` columns that we can also leverage to perform advanced data operations.

![Created column](https://paper-attachments.dropboxusercontent.com/s_E1CBF4A45089EFC3D9C461B80BB9F97D882823FECDCA010DD86FEAC6EC26A79C_1706441400678_Screenshot+2024-01-28+at+12.19.01.png)

**Get the Database URL and set up the API Key**

To securely connect to the database, Xata provides a unique and secure URL for accessing it. To get the database URL, click the **Get code snippet** button and copy the URL. Then click the **API Key** link, add a new key, save and copy the API key.

![](https://paper-attachments.dropboxusercontent.com/s_E1CBF4A45089EFC3D9C461B80BB9F97D882823FECDCA010DD86FEAC6EC26A79C_1706442511712_Screenshot+2024-01-20+at+22.40.52.png)
![](https://paper-attachments.dropboxusercontent.com/s_E1CBF4A45089EFC3D9C461B80BB9F97D882823FECDCA010DD86FEAC6EC26A79C_1706442537662_Screenshot+2024-01-20+at+22.42.04.png)
![](https://paper-attachments.dropboxusercontent.com/s_724B5DB0ED02FBEDE105C37DC2102A0D29EC66EAFDCC27146EA9AEF451272040_1703596030165_Screenshot+2023-12-21+at+23.42.03.png)

**Setup environment variable**

Next, we must add our database URL and API key as an environment variable. To do this, create `.env` file in the root directory and add the copied URL and API key.

```bash
XATA_DATABASE_URL= <REPLACE WITH THE COPIED DATABASE URL>
XATA_API_KEY=<REPLACE WITH THE COPIED API KEY>
```

## Building the project management API with Go and Xata

We can start building our API with our database fully set up on Xata and a secure URL created to access it.

**Create the API models**

To represent the application data, we need to create a `model.go` file inside the `data` folder and add the snippet below:

```go
package data

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

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

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

The snippet above creates a `Project`, `ProjectRequest`, and `ProjectResponse` struct with the required properties to describe requests and response types.

**Create the API routes**

With the models fully set up, we need to navigate to the `api` folder and create a `route.go` file for configuring the API routes and add the snippet below:

```go
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 `Config` struct with a `Router` property to configure the application methods
-   Creates a `Routes` function that takes in the `Config` struct as a pointer

**Create the API helpers**

Next, we will create helper functions for our application to construct the API. To do this, we need to create a `helper.go` file inside the `api` folder and add the snippet below:

```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()})
}
```

The snippet above does the following:

-   Imports the required dependencies
-   Creates a `jsonResponse` struct to describe the API response
-   Creates a `GetEnvVariable` function that uses `godotenv` package to load and get environment variable
-   Creates a `validateBody` function that takes in the `Config` struct as a pointer and returns an `error`. Inside the function, we validate that request `data` are in the correct format using the validator library
-   Creates a `writeJSON` function that takes in the `Config` struct as a pointer and uses the `jsonResponse` struct to construct API response when there’s no error
-   Creates a `errorJSON` function that takes in the `Config` struct as a pointer and uses the `jsonResponse` struct to construct API response when there’s an error

**Create the API services**

With our application models fully set up, we can now use them to create our application logic. To do this, we need to create a `xata_service.go` file and update it by doing the following:

First, we need to import the required dependencies and create helper functions:

```go
package api

import (
    "bytes"
    "encoding/json"
    "fmt"
    "go-xata/data"
    "io/ioutil"
    "net/http"
)

var xataAPIKey = GetEnvVariable("XATA_API_KEY")
var baseURL = GetEnvVariable("XATA_DATABASE_URL")

func createRequest(method, url string, bodyData *bytes.Buffer) (*http.Request, error) {
    var req *http.Request
    var err error

    if method == "GET" || method == "DELETE" {
        req, err = http.NewRequest(method, url, nil)
    } else {
        req, err = http.NewRequest(method, url, bodyData)
    }

    if err != nil {
        return nil, err
    }

    req.Header.Add("Content-Type", "application/json")
    req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", xataAPIKey))
    return req, nil
}

func makeRequest(req *http.Request, target interface{}) error {
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    if target != nil {
        body, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            return err
        }
        err = json.Unmarshal(body, target)
        if err != nil {
            return err
        }
    }
    return nil
}
```

The snippet above does the following:

-   Imports the required dependencies
-   Creates required environment variables
-   Creates a `createRequest` function creates HTTP request with the required headers
-   Creates a `makeRequest` function that handles sending the request and parsing the response

Lastly, we need to add methods that use the helper methods to perform CRUD operations.

```go
//imports goes here

var xataAPIKey = GetEnvVariable("XATA_API_KEY")
var baseURL = GetEnvVariable("XATA_DATABASE_URL")

func createRequest(method, url string, bodyData *bytes.Buffer) (*http.Request, error) {
    //createRequest code goes here
}

func makeRequest(req *http.Request, target interface{}) error {
    //makeRequest code goes here
}

func (app *Config) createProjectService(newProject *data.ProjectRequest) (*data.ProjectResponse, error) {
    createProject := data.ProjectResponse{}
    jsonData := data.Project{
        Name:        newProject.Name,
        Description: newProject.Description,
        Status:      newProject.Status,
    }

    postBody, _ := json.Marshal(jsonData)
    bodyData := bytes.NewBuffer(postBody)

    fullURL := fmt.Sprintf("%s:main/tables/Project/data", baseURL)
    req, err := createRequest("POST", fullURL, bodyData)
    if err != nil {
        return nil, err
    }

    err = makeRequest(req, &createProject)
    if err != nil {
        return nil, err
    }

    return &createProject, nil
}

func (app *Config) getProjectService(projectId string) (*data.Project, error) {
    projectDetails := data.Project{}
    fullURL := fmt.Sprintf("%s:main/tables/Project/data/%s", baseURL, projectId)
    req, err := createRequest("GET", fullURL, nil)
    if err != nil {
        return nil, err
    }

    err = makeRequest(req, &projectDetails)
    if err != nil {
        return nil, err
    }

    return &projectDetails, nil
}

func (app *Config) updateProjectService(updatedProject *data.ProjectRequest, projectId string) (*data.ProjectResponse, error) {
    updateProject := data.ProjectResponse{}
    jsonData := data.Project{
        Name:        updatedProject.Name,
        Description: updatedProject.Description,
        Status:      updatedProject.Status,
    }

    postBody, _ := json.Marshal(jsonData)
    bodyData := bytes.NewBuffer(postBody)

    fullURL := fmt.Sprintf("%s:main/tables/Project/data/%s", baseURL, projectId)
    req, err := createRequest("PUT", fullURL, bodyData)
    if err != nil {
        return nil, err
    }

    err = makeRequest(req, &updateProject)
    if err != nil {
        return nil, err
    }

    return &updateProject, nil
}

func (app *Config) deleteProjectService(projectId string) (string, error) {
    fullURL := fmt.Sprintf("%s:main/tables/Project/data/%s", baseURL, projectId)
    req, err := createRequest("DELETE", fullURL, nil)
    if err != nil {
        return "", err
    }

    err = makeRequest(req, nil)
    if err != nil {
        return "", err
    }
    return projectId, nil
}
```

The snippet above creates a `createProjectService`, `getProjectService`, `updateProjectService`, and `deleteProjectService` methods for performing CRUD operations.

**Create the API handlers**

With that done, we can use the services to create our API handlers. To do this, we need to create a `handler.go` file inside `api` folder and add the snippet below:

```go
package api

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

const appTimeout = time.Second * 10

func (app *Config) createProjectHandler() 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,
            Status:      payload.Status,
        }

        data, err := app.createProjectService(&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.getProjectService(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,
            Status:      payload.Status,
        }

        data, err := app.updateProjectService(&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.deleteProjectService(projectId)
        if err != nil {
            app.errorJSON(ctx, err)
            return
        }

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

The snippet above does the following:

-   Imports the required dependencies
-   Creates a `createdProjectHandler`, `getProjectHandler`, `updateProjectHandler`, and `deleteProjectHandler` functions that return a Gin-gonic handler and takes in the `Config` struct as a pointer. Inside the returned handler, we defined the API timeout, used the helper functions and the service created earlier to perform the corresponding action.

**Update the API routes to use handlers**

With that done, we can now update the `routes.go` file with the handlers as shown below:

```go
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 our API fully set up, we must create the application entry point. To do this, we need to create a `main.go` file inside the `cmd` folder and add the snippet below:

```go
package main

import (
    "go-xata/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 `Default` configuration
-   Initialize the `Config` struct by passing in the `Router`
-   Adds the route and run the application on port `:8080`

With that done, we can start a development server using the command below:

```bash
go run cmd/main.go
```

![Create project](https://paper-attachments.dropboxusercontent.com/s_E1CBF4A45089EFC3D9C461B80BB9F97D882823FECDCA010DD86FEAC6EC26A79C_1706483516010_Screenshot+2024-01-29+at+00.09.09.png)
![Get a project](https://paper-attachments.dropboxusercontent.com/s_E1CBF4A45089EFC3D9C461B80BB9F97D882823FECDCA010DD86FEAC6EC26A79C_1706483516012_Screenshot+2024-01-29+at+00.09.28.png)
![Update a project](https://paper-attachments.dropboxusercontent.com/s_E1CBF4A45089EFC3D9C461B80BB9F97D882823FECDCA010DD86FEAC6EC26A79C_1706483516046_Screenshot+2024-01-29+at+00.10.05.png)

![Delete a project](https://paper-attachments.dropboxusercontent.com/s_E1CBF4A45089EFC3D9C461B80BB9F97D882823FECDCA010DD86FEAC6EC26A79C_1706483516086_Screenshot+2024-01-29+at+00.11.02.png)

We can also confirm the project management data by checking the table on Xata.

![Data on Xata](https://paper-attachments.dropboxusercontent.com/s_E1CBF4A45089EFC3D9C461B80BB9F97D882823FECDCA010DD86FEAC6EC26A79C_1706483568817_Screenshot+2024-01-29+at+00.11.32.png)

# Conclusion

This post discusses what Xata is and provides a detailed step-by-step guide to using it to build a project management API in Go. In addition to the functionalities explored earlier, Xata also includes well-tailored features that developers can harness to build applications ranging from small to large.

These resources may also be helpful:

-   [Xata documentation](https://xata.io/docs?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog)
-   [Gin-gonic web framework](https://gin-gonic.com/?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog)
-   [Go HTTP Library](https://pkg.go.dev/net/http?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog)