How to use Appwrite cloud database in your Nuxt.js app
Tags: javascript, vue, tutorial, nuxt
Published: 2023-05-26 22:32:40

[Appwrite](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog) is an open-source and secure backend-as-a-service platform with database functions and other core APIs necessary to build server-like applications for web, mobile, and Flutter developers. Appwrite integrates with both client and server-side programming languages. 

[Appwrite Cloud](https://cloud.appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog) provides the same services as Appwrite, like functions, authentication, database, storage, etc. Using Appwrite Cloud, everything is managed directly from a dedicated URL instead of the local instance running on Docker. 

This tutorial will show you how to build a create, read, update, and delete (CRUD) application in NuxtJS, making use of Appwrite Cloud and [Pink Design](https://pink.appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog) suitable for frontend developers.


## Project overview

At the end of this lesson, the CRUD application should look something like this:


![Tooodooos app](https://paper-attachments.dropboxusercontent.com/s_98E65F8D31234D1FBC487A096B9A677CB51B08BD30207283061A296CA029A8CB_1683219496014_image.png)



## GitHub and demo

Check the complete source code for this project in [this repo](https://github.com/Terieyenike/appwrite-todos). Also, try the demo [here](https://appwrite-todos.vercel.app/).


## Prerequisites

The following are required to complete this tutorial:

- An understanding of JavaScript, Vue, and CSS
- [Node >= 16](https://nodejs.org/) for dependencies installation
- Access to an Appwrite Cloud account

Join and submit a request for Appwrite Cloud [here](https://appwrite.io/cloud).

## Setting up Appwrite Cloud

Log in to Appwrite Cloud and create a new project in your cloud instance by clicking the **+ Create project** button. 


![create project](https://paper-attachments.dropboxusercontent.com/s_98E65F8D31234D1FBC487A096B9A677CB51B08BD30207283061A296CA029A8CB_1682103509131_Screenshot+2023-04-20+at+13.22.44.jpg)


PS: make sure to give the project a desired name.


## Create a database

Navigate into the created project, click **Databases** on the window's left pane, and give your database a name.


![TooodooosDB](https://paper-attachments.dropboxusercontent.com/s_98E65F8D31234D1FBC487A096B9A677CB51B08BD30207283061A296CA029A8CB_1682104001011_Screenshot+2023-04-20+at+13.29.35.jpg)

## Creating collections

Appwrite uses collections as containers of documents. By clicking on the database name created, click the **Create collection** button and give the **Collections** a name.


![Collections](https://paper-attachments.dropboxusercontent.com/s_98E65F8D31234D1FBC487A096B9A677CB51B08BD30207283061A296CA029A8CB_1682104444344_image.png)



## Adding attributes

It is vital to create field parameters, as they will hold all the registered data in the database. To create attributes, navigate to the created **Collections** and click the **Attributes** tab. For this tutorial, the attributes are as follows:

| Attribute Key | Attribute type | Size | Default value | Required |
| ------------- | -------------- | ---- | ------------- | -------- |
| todo          | String         | 255  | -             | Yes      |


![create attribute](https://paper-attachments.dropboxusercontent.com/s_98E65F8D31234D1FBC487A096B9A677CB51B08BD30207283061A296CA029A8CB_1682104946490_Screenshot+2023-04-20+at+13.44.56.jpg)


Next, select the **Settings** tab within the Collections and update the permissions to manage users' access and rights.


![Permissions](https://paper-attachments.dropboxusercontent.com/s_98E65F8D31234D1FBC487A096B9A677CB51B08BD30207283061A296CA029A8CB_1682105442250_Screenshot+2023-04-20+at+13.48.20.jpg)


Finally, register your web app. Click the **Overview** tab on the left pane and **Add the web app platform**.


![Register your web app](https://paper-attachments.dropboxusercontent.com/s_98E65F8D31234D1FBC487A096B9A677CB51B08BD30207283061A296CA029A8CB_1682105981083_Screenshot+2023-04-20+at+13.23.33.jpg)


The **Hostname** with the asterisk **(*)** ensures access during development; otherwise, if not set, cross-origin resource sharing (CORS) may prevent access to the site data, thereby throwing errors.



## Scaffolding a Nuxt app 

Nuxt is a progressive open-source framework built on top of Vue. Let’s scaffold a new Nuxt application using the following command:

```
    npx nuxi init todo
```

Follow the instructions and run the provided commands like `yarn install`. This command will install all the required dependencies.

Next, navigate to the project directory, then **todos**, and start the development server in the terminal:

```
    cd todos && yarn dev
```

![Nuxt app](https://paper-attachments.dropboxusercontent.com/s_98E65F8D31234D1FBC487A096B9A677CB51B08BD30207283061A296CA029A8CB_1685351396337_image.png)


## Installing dependencies

As mentioned earlier, you need these two dependencies in the Nuxt app: Appwrite and Appwrite Pink Design.

In your terminal, run this command:

```
    yarn add appwrite
    yarn add @appwrite.io/pink
```

## Including Appwrite Pink in the project

Before you see the action of Appwrite CSS and its icons, let’s create a **pages** folder in the app's root directory. After that, create a file, `index.vue`,  and add the following code.

`pages/index.vue`
```javascript
    <template>
      <p class="heading-level-1">Add tooodooos</p>
    </template>
    
    <script setup lang="ts">
    import "@appwrite.io/pink";
    import "@appwrite.io/pink-icons";
    </script>
```

Next, in the entry point of the project, `app.vue`, change the component `NuxtWelcome` to `NuxtPage` like this:


`app.vue`
```
    <template>
      <NuxtPage />
    </template>
```

Here’s the result to confirm that the Appwrite CSS library is working:


![Heading 1 class to text](https://paper-attachments.dropboxusercontent.com/s_98E65F8D31234D1FBC487A096B9A677CB51B08BD30207283061A296CA029A8CB_1682108414230_image.png)


The project tree directory should look something like this:

```
    .
    ├── pages
    │   └── index.vue
    ├── public
    ├── package.json
    ├── tsconfig.json
    ├── yarn.lock
    ├── README.md
    ├── nuxt.config.js
    └── app.vue
```


## Building the UI

The user interface for the todos app will showcase all the todos created using an input field and list all of them with an edit and a delete icon.

Now, let’s update the app directory with a new directory called **components**, which will include the following files: `AboutTodo.vue`, `Header.vue`, `ListTodo.vue`, and `Todos.vue`. Also, create another file within the pages folder called `about.vue`.

The updated project tree directory:

```
    .
    ├── components
    │   ├── AboutTodo.vue
    │   ├── Header.vue
    │   ├── ListTodo.vue
    │   └── Todos.vue
    ├── pages
    │   ├── about.vue
    │   └── index.vue
    ├── public
    ├── package.json
    ├── tsconfig.json
    ├── yarn.lock
    ├── README.md
    ├── nuxt.config.js
    └── app.vue
```

Include the following code in the file components:

`components/AboutTodo.vue`
```javascript
    <template>
      <div class="container">
        <h2 class="eyebrow-heading-2">About Tooodooos</h2>
        <p class="text" :style="{ 'margin-top': 1 + 'rem' }">
          Using Appwrite functions and Appwrite Cloud, adding todos have become
          simpler for anyone wanting to create their own. Appwrite as a tool is a
          backend-as-a-service platform.
        </p>
        <p class="text" :style="{ 'margin-top': 1 + 'rem' }">
          The technology used to build this app is Nuxt, Appwrite Pink for the
          design system, and integrating Appwrite of course.
        </p>
      </div>
    </template>
```


The **AboutTodo** component will display the info about the project on navigating to the /about page when clicked in the navigation bar.

The code above centers the content of the **About** page using Appwrite Pink Design. Also included within this component are [Vue style bindings](https://vuejs.org/guide/essentials/class-and-style.html#binding-inline-styles) in the `<p>` element which sets a top margin of 1rem.

`components/Header.vue`
```javascript
    <template>
      <header
        class="u-flex u-main-space-between u-cross-center u-position-sticky"
        style="--inset-block-start: auto">
        <NuxtLink to="/" class="u-bold">Tooodooos</NuxtLink>
        <ul class="list">
          <li class="list-item">
            <span class="text">
              <NuxtLink to="/about">About</NuxtLink>
            </span>
          </li>
        </ul>
      </header>
    </template>
```

The code snippet above uses Appwrite’s Pink **Display** and **List** element to style the navigation bar. The **NuxtLink** component is also included for navigation to the home and about pages, respectively. 

`components/ListTodo.vue`
```js
    <template>
      <div class="u-flex u-main-space-between u-cross-center u-width-full-line">
        <span class="text">{{ item }}</span>
        <div class="u-cursor-pointer">
          <span
            class="icon-pencil"
            aria-hidden="true"
            :style="{ 'margin-right': space + 'em' }"></span>
          <span class="icon-trash" aria-hidden="true"></span>
        </div>
      </div>
    </template>
    
    <script setup>
    const props = defineProps({
      item: String,
    });
    
    const space = ref("1");
    </script>
```

The code above displays the todo list item from the **props** array and the pencil and trash icon from Pink Design.

`components/Todos.vue`
```js
    <template>
      <div class="container">
        <Header />
        <h1 class="heading-level-1" :style="{ 'margin-top': 1 + 'rem' }">
          {{ name }}
        </h1>
        <form
          class="form u-width-full-line u-max-width-500 u-flex u-main-center"
          :style="{ 'margin-top': 1 + 'em' }">
          <ul class="form-list">
            <li class="form-item">
              <label class="label">Todo</label>
              <div class="input-text-wrapper">
                <input
                  class="input-text"
                  type="text"
                  placeholder="add new todo"
                  v-model="input.todo" />
              </div>
            </li>
          </ul>
          <button class="button" :style="{ 'margin-top': 1 + 'em' }">
            <span class="text">Add todo</span>
          </button>
        </form>
        <div :style="{ 'margin-top': marginTop + 'em' }">
          <ul class="list">
            <li class="list-item">
              <list-todo item="Create API documentation" />
            </li>
          </ul>
        </div>
      </div>
    </template>
    
    <script setup>
    const name = ref("Add tooodooos");
    
    const marginTop = ref("3");
    
    const input = reactive({
      todo: "",
    });
    </script>
```

The following occurs in the code snippet above:

- In the script section, declare the variables using the composition API
- Pass these values in the `<template>`
- Import the **Header** and **list-todo** components
- Used the classes from Pink Design and defined `:style` bindings to the elements
- Pass the **item** props to the list-todo component

`pages/about.vue`
```js
    <template>
      <div class="container">
        <Header />
        <about-todo />
      </div>
    </template>
```

The code is responsible for importing the **Header** and **about-todo** components to display the content of the about page.


## Creating environment variables

As the deployed project is publicly available on GitHub, creating a local file, `.env`, that will include all your secret keys and constants is advisable. 

Check out [this guide](https://dev.to/hackmamba/how-to-create-environment-variables-in-nuxtjs-2dh4) on creating environment variables in a Nuxt.js app.


## Posting a new todo to Appwrite Cloud

The `create` operation in create, read, update, and delete (**CRUD)** will use the HTTP protocol method, **POST**, which could mean creating a new list, task, or post. This action will send this request to the database Appwrite Cloud.

Now, update the **Todos** component with the following code:

`components/Todos.vue`
```js
    <template>
      <div class="container">
        <Header />
        <h1 class="heading-level-1" :style="{ 'margin-top': 1 + 'rem' }">
          {{ name }}
        </h1>
        <form
          class="form u-width-full-line u-max-width-500 u-flex u-main-center"
          :style="{ 'margin-top': 1 + 'em' }"
          @submit.prevent="handleInputChange">
          <ul class="form-list">
            <li class="form-item">
              <label class="label">Todo</label>
              <div class="input-text-wrapper">
                <input
                  class="input-text"
                  type="text"
                  :placeholder="inputError ? 'please enter a todo' : 'add new todo'"
                  v-model="input.todo" />
              </div>
            </li>
          </ul>
          <button class="button" :style="{ 'margin-top': 1 + 'em' }">
            <span class="text">Add todo</span>
          </button>
        </form>
        <div :style="{ 'margin-top': marginTop + 'em' }">
          <ul class="list">
            <li class="list-item">
              <list-todo item="Create API documentation" />
            </li>
          </ul>
        </div>
      </div>
    </template>
    
    <script setup>
    import { Client, Databases, ID } from "appwrite";
    
    const client = new Client();
    const databases = new Databases(client);
    
    const runtimeConfig = useRuntimeConfig();
    
    client
      .setEndpoint(runtimeConfig.public.API_ENDPOINT)
      .setProject(runtimeConfig.public.PROJECT_ID);
    
    const name = ref("Add tooodooos");
    
    const marginTop = ref("3");
    
    const input = reactive({
      todo: "",
    });
    
    const inputError = ref(false);
    
    const create = (data) =>
      databases.createDocument(
        runtimeConfig.public.DATABASE_ID,
        runtimeConfig.public.COLLECTION_ID,
        ID.unique(),
        data
      );
    
    const handleInputChange = () => {
      if (!input.todo) {
        return (inputError.value = true);
      }
      create({
        todo: input.todo,
      }).then(
        function (response) {
          window.location.reload();
        },
        function (error) {
          console.log(error);
        }
      );
    };
    </script>
```

The following occurs in this code snippet:

- Import the Appwrite package and initialize a new instance of the web SDK
- Using the `useRuntimeConfig()`, you can access the environment variables
- The `inputError` variable set to false is helpful for error checking when the user tries to send an empty input field by binding the `:placeholder` attribute
- The `create` function with the parameter **data** is to connect the attribute key, **todo** in Appwrite Cloud with the value passed in the `<input>` element using the directive `v-model`
- The function `handleInputChange` passed to the `<form>` using the `@submit.prevent` directive will send the typed value in the input field to the Appwrite server
- With every new todo added, the pages refresh with the `window.location.reload()` function


## Displaying all the todos list 

This section shows the list of todos from the server onto the application's client-side using the **read** operation.

Again, update the Todos component with this code:

`components/Todos.vue`
```js
    <template>
      <div class="container">
        <Header />
        <h1 class="heading-level-1" :style="{ 'margin-top': 1 + 'rem' }">
          {{ name }}
        </h1>
        <form
          class="form u-width-full-line u-max-width-500 u-flex u-main-center"
          :style="{ 'margin-top': 1 + 'em' }"
          @submit.prevent="handleInputChange">
          <ul class="form-list">
            <li class="form-item">
              <label class="label">Todo</label>
              <div class="input-text-wrapper">
                <input
                  class="input-text"
                  type="text"
                  :placeholder="inputError ? 'please enter a todo' : 'add new todo'"
                  v-model="input.todo" />
              </div>
            </li>
          </ul>
          <button class="button" :style="{ 'margin-top': 1 + 'em' }">
            <span class="text">Add todo</span>
          </button>
        </form>
        <div :style="{ 'margin-top': marginTop + 'em' }">
          <ul class="list">
            <li class="list-item" v-for="item in todos" :key="item.$id">
              <list-todo :item="item" />
            </li>
          </ul>
        </div>
      </div>
    </template>
    <script setup>
    ...
    
    const todos = ref(null);
    
    const getTodo = databases.listDocuments(
      runtimeConfig.public.DATABASE_ID,
      runtimeConfig.public.COLLECTION_ID
    );
    
    onMounted(() => {
      getTodo.then(
        function (response) {
          todos.value = response.documents;
        },
        function (error) {
          console.log(error);
        }
      );
    });
    </script>
```

The code above is responsible for the following:

- The `getTodo` function is responsible for listing all of the todo items in the app within the `onMounted` lifecycle hook
- Looping through the array using the `v-for` directive on the **<li>** element and replacing the previous item props with the **v-bind:item** or **:item** attribute on the **list-todo** component

## Updating a todo item

Correcting your list items (todos) is crucial for any CRUD app using the **UPDATE** operation. Copy-paste this updated code:

`components/ListTodo.vue`
```js
    <template>
      <div class="u-flex u-main-space-between u-cross-center u-width-full-line">
        <span class="text">{{ item.todo }}</span>
        <div class="u-cursor-pointer">
          <span
            class="icon-pencil"
            aria-hidden="true"
            @click.prevent="showModal = !showModal"
            :style="{ 'margin-right': space + 'em' }"></span>
          <span class="icon-trash" aria-hidden="true"></span>
        </div>
      </div>
      <div
        v-if="showModal"
        class="u-z-index-20 u-padding-24"
        :style="{
          position: 'fixed',
          top: '0',
          right: 0,
          left: 0,
          bottom: 0,
          'background-color': 'rgba(0, 0, 0, 0.8)',
          height: '100vh',
        }">
        <form
          class="form u-width-full-line u-max-width-500 u-flex u-main-center"
          @submit.prevent="handleUpdateTodo">
          <ul class="form-list">
            <li class="form-item">
              <label class="label" :style="{ color: 'white' }">Edit todo</label>
              <div class="input-text-wrapper">
                <input
                  class="input-text"
                  type="text"
                  v-model="item.todo" />
              </div>
            </li>
          </ul>
          <button class="button" :style="{ 'margin-top': 1 + 'em' }">
            <span class="text">Update todo</span>
          </button>
        </form>
        <div class="u-cursor-pointer">
          <span
            class="icon-x u-font-size-32"
            @click.prevent="showModal = !showModal"
            aria-hidden="true"
            :style="{
              position: 'absolute',
              top: '0',
              right: '1em',
              color: '#fff',
            }"></span>
        </div>
      </div>
    </template>
    
    <script setup>
    import { Client, Databases } from "appwrite";
    
    const client = new Client();
    const databases = new Databases(client);
    
    const runtimeConfig = useRuntimeConfig();
    
    client
      .setEndpoint(runtimeConfig.public.API_ENDPOINT)
      .setProject(runtimeConfig.public.PROJECT_ID);
    
    const props = defineProps({
      item: Object,
    });
    
    const space = ref("1");
    
    const showModal = ref(false);
    
    const updateTodo = (database_id, collection_id, document_id, data) =>
      databases.updateDocument(database_id, collection_id, document_id, data);
    
    const handleUpdateTodo = () => {
      updateTodo(props.item.$databaseId, props.item.$collectionId, props.item.$id, {
        todo: props.item.todo,
      }).then(
        function (response) {
          console.log(`${props.item.todo} successfully updated in DB`);
        },
        function (error) {
          console.log("Error updating the document", error.message);
        }
      );
    };
    </script>
```

This code snippet aims to update a document with its unique ID. Using the patch method, it updates only the partial data:

- The variable `showModal` populates the modal with a click on the pencil icon
- Within the `handleUpdateTodo` function, passing the object with a key **todo** and the value passed in the **<input>** to show the exact data using the directive, **v-model**


## Deleting a todo list

The last operation for this CRUD app is to use the **delete** method to delete the data from the client-side and the server. 

Let’s update the **ListTodo** component with this code:

`components/ListTodo.vue`
```js
    <template>
      <div class="u-flex u-main-space-between u-cross-center u-width-full-line">
        <span class="text">{{ item.todo }}</span>
        <div class="u-cursor-pointer">
          <span
            class="icon-pencil"
            aria-hidden="true"
            @click.prevent="showModal = !showModal"
            :style="{ 'margin-right': space + 'em' }"></span>
          <span
            class="icon-trash"
            aria-hidden="true"
            @click.prevent="handleDeleteTodo"></span>
        </div>
      </div>
      <div
        v-if="showModal"
        class="u-z-index-20 u-padding-24"
        :style="{
          position: 'fixed',
          top: '0',
          right: 0,
          left: 0,
          bottom: 0,
          'background-color': 'rgba(0, 0, 0, 0.8)',
          height: '100vh',
        }">
        <form
          class="form u-width-full-line u-max-width-500 u-flex u-main-center"
          @submit.prevent="handleUpdateTodo">
          <ul class="form-list">
            <li class="form-item">
              <label class="label" :style="{ color: 'white' }">Edit todo</label>
              <div class="input-text-wrapper">
                <input class="input-text" type="text" v-model="item.todo" />
              </div>
            </li>
          </ul>
          <button class="button" :style="{ 'margin-top': 1 + 'em' }">
            <span class="text">Update todo</span>
          </button>
        </form>
        <div class="u-cursor-pointer">
          <span
            class="icon-x u-font-size-32"
            @click.prevent="showModal = !showModal"
            aria-hidden="true"
            :style="{
              position: 'absolute',
              top: '0',
              right: '1em',
              color: '#fff',
            }"></span>
        </div>
      </div>
    </template>
    
    <script setup>
    ...
    
    const deleteTodo = (database_id, collection_id, document_id) =>
      databases.deleteDocument(database_id, collection_id, document_id);
    
    const handleDeleteTodo = () => {
      deleteTodo(
        props.item.$databaseId,
        props.item.$collectionId,
        props.item.$id
      ).then(
        function (response) {
          window.location.reload();
        },
        function (error) {
          console.log(error);
        }
      );
    };
    </script>
```

The `handleDeleteTodo` function deletes a todo item with a unique ID from both the client-side and the server with the **@click** event on the `<span>` delete icon.

At this point, the UI will look like this:


{% embed https://www.youtube.com/watch?v=Z4btHWO0Et8 %}


## Conclusion

This tutorial showed you how to build a todo CRUD application and pair its functionalities with Appwrite Cloud to create, store, update, and delete data from the client and server.


## Resources
- [Appwrite](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog)
- [Appwrite Cloud](https://cloud.appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog)
- [Appwrite Databases API](https://appwrite.io/docs/client/databases?utm_source=hackmamba&utm_medium=hackmamba-blog)