Xata + Rust: A getting started guide.
Tags: xata, rust, restapi, actixweb
Published: 2024-02-05 09:13:46

[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 Rust. 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/rust-xata).

## Prerequisites

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

-   Basic understanding of Rust
-   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:

    cargo new rust-xata && cd rust-xata

This command creates a Rust project called `rust-xata` and navigates into the project directory.

Next, we install the required dependencies by modifying the `[dependencies]` section of the `Cargo.toml` file as shown below:

```toml
[dependencies]
actix-web = "4.4.1"
serde = { version = "1.0.195", features = ["derive"] }
serde_json = "1.0.111"
dotenv = "0.15.0"
reqwest = { version = "0.11.23", features = ["json"] }
```

`actix-web = "4.4.1"` is a Rust-based framework for building web applications.

`serde = { version = "1.0.145", features = ["derive"] }` for serializing and deserializing Rust data structures.

`serde_json = "1.0"` for manipulating JSON and vice versa.

`reqwest = { version = "0.11", features = ["json"] }` for making HTTP requests.

We need to run the command below to install the dependencies:

```bash
cargo build
```

**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 need to navigate to the `src` directory and, in this folder, create an `api` folder. In the `api` folder, we also need to create a `mod.rs`, `models.rs`, `services.rs`, and `handlers.rs` files.

`mod.rs` is a file for managing application visibility.

`models.rs` is for structuring our application data.

`xata_services.rs` is for abstracting our application logic.

`handlers.rs` is for structuring our APIs.

Next, we need to declare these files as a module by importing them into the `mod.rs` file

```rust
pub mod handlers;
pub mod models;
pub mod services;
```

Finally, we need to register `api` folder as a parent module by importing it into the `main.rs` file as shown below:

```rust
mod api; //add

fn main() {
    println!("Hello world")
}
```

## 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)

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.

![click the Get code snippet button](https://paper-attachments.dropboxusercontent.com/s_E1CBF4A45089EFC3D9C461B80BB9F97D882823FECDCA010DD86FEAC6EC26A79C_1706442511712_Screenshot+2024-01-20+at+22.40.52.png)
![copy URL](https://paper-attachments.dropboxusercontent.com/s_E1CBF4A45089EFC3D9C461B80BB9F97D882823FECDCA010DD86FEAC6EC26A79C_1706442537662_Screenshot+2024-01-20+at+22.42.04.png)
![create API key](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 Rust 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 update the `models.rs` file as shown below:

```rust
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Project {
    pub id: String,
    pub name: String,
    pub description: String,
    pub status: String,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ProjectRequest {
    pub name: String,
    pub description: String,
    pub status: String,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ProjectResponse {
    pub id: String,
}

#[derive(Serialize, Debug, Clone)]
pub struct APIResponse<T> {
    pub status: u16,
    pub message: String,
    pub data: Option<T>,
}

#[derive(Serialize, Debug, Clone)]
pub struct APIErrorResponse {
    pub status: u16,
    pub message: String,
    pub data: Option<String>,
}
```

The snippet above does the following:

-   Imports the required dependency
-   Creates a `Project`, `ProjectRequest`, and `ProjectResponse` structs with required properties to describe request and response body accordingly
-   Creates an `APIResponse`, and `APIErrorResponse` structs with the required properties needed for the API response

**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 update the `xata_service.rs` file by doing the following:

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

```rust
use std::env;
use dotenv::dotenv;
use reqwest::{header, Client, Error, RequestBuilder};
use super::model::{Project, ProjectRequest, ProjectResponse};

pub struct XataService;

impl XataService {
    fn env_loader(key: &str) -> String {
        dotenv().ok();
        match env::var(key) {
            Ok(v) => v.to_string(),
            Err(_) => format!("Error loading env variable"),
        }
    }

    fn init() -> Client {
        Client::new()
    }

    fn create_headers() -> header::HeaderMap {
        let mut headers = header::HeaderMap::new();
        headers.insert("Content-Type", "application/json".parse().unwrap());
        headers.insert(
            header::AUTHORIZATION,
            format!("Bearer {}", XataService::env_loader("XATA_API_KEY"))
                .parse()
                .unwrap(),
        );
        headers
    }

    async fn send_request(builder: RequestBuilder) -> Result<reqwest::Response, reqwest::Error> {
        builder.send().await
    }

    async fn handle_response<T>(response: reqwest::Response) -> Result<T, reqwest::Error>
    where
        T: serde::de::DeserializeOwned,
    {
        let json = response.text().await?;
        Ok(serde_json::from_str(json.as_str()).unwrap())
    }
}
```

The snippet above does the following:

-   Imports the required dependencies
-   Creates an `XataService` struct
-   Creates an implementation block that adds `env_loader`, `init`, `create_headers`, `send_request`, and `handle_response` helper methods. These methods are designed to load environment variables, create a connection pool for making asynchronous requests, generate request headers, handle the sending of requests, and manage the deserialization of JSON responses.

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

```rust
//imports goes here

pub struct XataService;

impl XataService {
    //helper methodes goes here

    pub async fn create_project(new_project: ProjectRequest) -> Result<ProjectResponse, Error> {
        let url = format!(
            "{}:main/tables/Project/data",
            XataService::env_loader("XATA_DATABASE_URL")
        );

        let client = XataService::init()
            .post(url)
            .headers(XataService::create_headers())
            .json(&new_project);

        let response = XataService::send_request(client).await?;
        XataService::handle_response(response).await
    }

    pub async fn get_project(project_id: String) -> Result<Project, Error> {
        let url = format!(
            "{}:main/tables/Project/data/{}",
            XataService::env_loader("XATA_DATABASE_URL"),
            project_id
        );

        let client = XataService::init()
            .get(url)
            .headers(XataService::create_headers());

        let response = XataService::send_request(client).await?;
        XataService::handle_response(response).await
    }

    pub async fn update_project(
        updated_project: ProjectRequest,
        project_id: String,
    ) -> Result<ProjectResponse, Error> {
        let url = format!(
            "{}:main/tables/Project/data/{}",
            XataService::env_loader("XATA_DATABASE_URL"),
            project_id
        );

        let client = XataService::init()
            .put(url)
            .headers(XataService::create_headers())
            .json(&updated_project);

        let response = XataService::send_request(client).await?;
        XataService::handle_response(response).await
    }

    pub async fn delete_project(project_id: String) -> Result<String, Error> {
        let url = format!(
            "{}:main/tables/Project/data/{}",
            XataService::env_loader("XATA_DATABASE_URL"),
            project_id
        );

        let client = XataService::init()
            .delete(url)
            .headers(XataService::create_headers());

        let _response = XataService::send_request(client).await?;
        Ok(format!(
            "Project with ID: {} deleted successfully!!",
            project_id
        ))
    }
}
```

The snippet above creates a `create_project`, `get_project`, `update_project`, and `delete_project` 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, first, we need to add the snippet below to the `handlers.rs` file:

```rust
use actix_web::{
    delete, get, post, put,
    web::{Json, Path},
    HttpResponse,
};
use reqwest::StatusCode;
use super::{
    model::{APIErrorResponse, APIResponse, Project, ProjectRequest, ProjectResponse},
    xata_service::XataService,
};

#[post("/project")]
pub async fn create_project_handler(new_project: Json<ProjectRequest>) -> HttpResponse {
    let project_details = XataService::create_project(new_project.to_owned()).await;

    match project_details {
        Ok(data) => HttpResponse::Accepted().json(APIResponse::<ProjectResponse> {
            status: StatusCode::CREATED.as_u16(),
            message: "success".to_string(),
            data: Some(data),
        }),
        Err(error) => HttpResponse::InternalServerError().json(APIErrorResponse {
            status: StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
            message: "failure".to_string(),
            data: Some(error.to_string()),
        }),
    }
}

#[get("/project/{id}")]
pub async fn get_project_handler(path: Path<String>) -> HttpResponse {
    let id = path.into_inner();
    if id.is_empty() {
        return HttpResponse::BadRequest().json(APIErrorResponse {
            status: StatusCode::BAD_REQUEST.as_u16(),
            message: "failure".to_string(),
            data: Some("invalid ID".to_string()),
        });
    };

    let project_details = XataService::get_project(id).await;

    match project_details {
        Ok(data) => HttpResponse::Accepted().json(APIResponse::<Project> {
            status: StatusCode::OK.as_u16(),
            message: "success".to_string(),
            data: Some(data),
        }),
        Err(error) => HttpResponse::InternalServerError().json(APIErrorResponse {
            status: StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
            message: "failure".to_string(),
            data: Some(error.to_string()),
        }),
    }
}
```

The snippet above does the following:

-   Imports the required dependencies
-   Creates a `create_project_handler` and `get_project_handler` handler with corresponding API routes that use the services to perform the corresponding actions and return the appropriate response using the `APIResponse` and `APIErrorResponse`

Lastly, we need to add `update_project_handler` and `delete_project_handler` handlers that use logic similar to the handlers above to update and delete a project.

```rust
//imports goes here

#[post("/project")]
pub async fn create_project_handler(new_project: Json<ProjectRequest>) -> HttpResponse {
    //create_project_handler code goes here
}

#[get("/project/{id}")]
pub async fn get_project_handler(path: Path<String>) -> HttpResponse {
    //get_project_handler code goes here
}

#[put("/project/{id}")]
pub async fn update_project_handler(
    updated_project: Json<ProjectRequest>,
    path: Path<String>,
) -> HttpResponse {
    let id = path.into_inner();
    if id.is_empty() {
        return HttpResponse::BadRequest().json(APIErrorResponse {
            status: StatusCode::BAD_REQUEST.as_u16(),
            message: "failure".to_string(),
            data: Some("invalid ID".to_string()),
        });
    };

    let project_details = XataService::update_project(updated_project.to_owned(), id).await;

    match project_details {
        Ok(data) => HttpResponse::Accepted().json(APIResponse::<ProjectResponse> {
            status: StatusCode::OK.as_u16(),
            message: "success".to_string(),
            data: Some(data),
        }),
        Err(error) => HttpResponse::InternalServerError().json(APIErrorResponse {
            status: StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
            message: "failure".to_string(),
            data: Some(error.to_string()),
        }),
    }
}

#[delete("/project/{id}")]
pub async fn delete_project_handler(path: Path<String>) -> HttpResponse {
    let id = path.into_inner();
    if id.is_empty() {
        return HttpResponse::BadRequest().json(APIErrorResponse {
            status: StatusCode::BAD_REQUEST.as_u16(),
            message: "failure".to_string(),
            data: Some("invalid ID".to_string()),
        });
    };

    let project_details = XataService::delete_project(id).await;

    match project_details {
        Ok(data) => HttpResponse::Accepted().json(APIResponse::<String> {
            status: StatusCode::ACCEPTED.as_u16(),
            message: "success".to_string(),
            data: Some(data),
        }),
        Err(error) => HttpResponse::InternalServerError().json(APIErrorResponse {
            status: StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
            message: "failure".to_string(),
            data: Some(error.to_string()),
        }),
    }
}
```

**Putting it all together**

With that done, we must update the `main.rs` file to include our application entry point and use the handlers.

```rust
use actix_web::{App, HttpServer};
use api::handler::{
    create_project_handler, delete_project_handler, get_project_handler, update_project_handler,
};
mod api;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(move || {
        App::new()
            .service(create_project_handler)
            .service(get_project_handler)
            .service(update_project_handler)
            .service(delete_project_handler)
    })
    .bind(("localhost", 8080))?
    .run()
    .await
}
```

The snippet above does the following:

-   Imports the required dependencies
-   Creates a new server that adds the handlers and runs on `localhost:8080`

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

```rust
cargo run main
```

![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 discussed what Xata is and provided a detailed step-by-step guide to use it to build a project management API in Rust. 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)
-   [Actix web framework](https://actix.rs/?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog)