Create a Real-Time Food Ordering Notification Service in Vue.js
Tags: javascript, webdev, tutorial, vue
Published: 2022-05-31 11:08:21

Notifications compel users to pay attention to specific pieces of information and encourage user interaction with our content or website.

## What we will be building

This post will discuss managing real-time app notifications in Vue.js to create a food ordering notification service. We will subscribe to channels in our database and display notifications when changes occur in the channels.

**GitHub URL**


https://github.com/Iheanacho-ai/appwrite-food-notification



## Prerequisites 

To get the most out of this project, we require the following:

- A basic understanding of CSS, JavaScript, and Vue.js
- Docker Desktop is installed on the computer, run the `docker -v` command to verify if we have Docker Desktop installed, if not, install it from the [Get Docker documentation](https://docs.docker.com/get-docker/) 
- An Appwrite instance running on our computer, check out [this article](https://dev.to/hackmamba/create-a-local-appwrite-instance-in-3-steps-19n9?utm_source=hackmamba&utm_medium=hackmamba) to create a local Appwrite instance, we will use Appwrite’s robust database and Realtime service to manage our application
    
## Setting up our Vue.js application

We install the [Vue CLI](https://cli.vuejs.org/) by running these terminal commands to create a new Vue project.

```bash
    npm install -g @vue/cli
    # OR
    yarn global add @vue/cli
```
After installing the Vue CLI, we navigate to our preferred directory and create a new project.

```bash
    vue create <name of our project>
```
We change the directory to the project and start a development server with:

```bash
    npm run serve
```

To see the app, we go to http://localhost:8080/


## Installing Dependencies

**Installing Tailwind CSS**

[Tailwind CSS](https://tailwindcss.com/) is a "utility-first" CSS framework that allows us to create user interfaces for web applications rapidly.

To install Tailwind CSS in our project, we run these terminal commands.

```bash
    npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
    npx tailwindcss init -p
```
These commands create two files in the root directory of our project, `tailwind.config.js` and `postcss.config.js`.

In our `tailwind.config.js`, we add the paths to all our template files with this code below.

```js
    module.exports = {
      purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
      content: [],
      theme: {
        extend: {},
      },
      plugins: [],
    }
```
Next, we add the tailwind directives in our `src/index.css` file.

```css
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
```

**Installing Vue Router**

Routing is the technology used to switch between different pages in our application based on the changes in the current URL.

We run this terminal command to enable our application to use [Vue Router](https://router.vuejs.org/).

```bash
    npm install vue-router@4
    
    #or 
    
    yarn add vue-router@4
```    

**Installing Mosha Vue Toastify**

[Mosha Vue Toastify](https://www.npmjs.com/package/mosha-vue-toastify) is a lightweight Vue library that allows us to create super customizable notifications. 

We run these commands to install the Mosha Vue Toastify library in our project.

```bash
    npm install mosha-vue-toastify
    
    #or
    
    yarn add mosha-vue-toastify
```
**Installing Appwrite**

[Appwrite](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog) is an open-source, end-to-end, back-end server solution that allows developers to build applications faster.

To use Appwrite in our Vue application, we install the Appwrite client-side SDK for web applications.

```bash
    npm install appwrite
```

## Creating a new Appwrite project

During the creation of the Appwrite instance, we specified what hostname and port we see our console. The default value is [localhost:80](http://localhost/).

We go to [localhost:80](http://localhost/) and create a new account to see our console.

On our console, there is a **Create Project** button. Click on it to start a new project.

![Appwrite Console](https://paper-attachments.dropbox.com/s_314B50E78D2533608F0DEE9BE97FEF3C5B7916B2F8CAC55559F4DCA3A07D86F6_1648400276700_Screenshot+64.png)


Our project dashboard appears once we have created the project. At the top of the page, there is a **Settings** bar. Click it to access the **Project ID** and **API Endpoint**.  


![Appwrite Console Dashboard](https://paper-attachments.dropbox.com/s_77D59B81C7D6F6CD823AD3176FAA8A00421C016326171AF8CEA737818A84C64C_1650013454801_Screenshot+122-2.png)


We copy the **Project ID** and **API Endpoint**, which we need to initialize the Appwrite Web SDK.

![Appwrite Console](https://paper-attachments.dropbox.com/s_77D59B81C7D6F6CD823AD3176FAA8A00421C016326171AF8CEA737818A84C64C_1650013478084_Screenshot+123.png)


In our `utils/web-init.js` file, we initialize our Web SDK with this piece of code.

```js
    import { Appwrite } from 'appwrite';
    export const sdk = new Appwrite();
    sdk
      .setEndpoint('http://localhost/v1') // Replace this with your endpoint
      .setProject('projectID'); // Replace this with your ProjectID
```    

**Creating an anonymous user session**

Appwrite requires a user to sign in before reading or writing to a database to enable safety in our application. However, they allow us to create an anonymous session to bypass that policy for simple projects.

We create an anonymous user session in our `utils.js` file.

```js
    import { Appwrite } from 'appwrite';
    export const sdk = new Appwrite();
    sdk
      .setEndpoint('http://localhost/v1') // Replace this with your endpoint
      .setProject('62532ce64d84a58176da'); // Replace this with your ProjectID
    
    // Create an anonymous user session
    
    sdk.account.createAnonymousSession().then(
       (response) => {
         console.log(response);
       },
       (error) => {
         console.log(error);
       }
    );
```
**Creating the collection and attributes**

Next, we set up our database that will store our order status. In the Appwrite web [Console](http://localhost/console), we click on **Database** on the left side of the dashboard.


![Appwrite Console Dashboard](https://paper-attachments.dropbox.com/s_77D59B81C7D6F6CD823AD3176FAA8A00421C016326171AF8CEA737818A84C64C_1650046806902_Screenshot+124.png)


We create a collection in our database tab by clicking on the **Add Collection** button. This action redirects us to a **Permissions** page.

At the **Collection Level**, we want to assign a **Read Access** and **Write Access** with a **role:all** value. We can modify the permissions to specify who has access to read or write to our database.


![Appwrite Console Permission Page](https://paper-attachments.dropbox.com/s_77D59B81C7D6F6CD823AD3176FAA8A00421C016326171AF8CEA737818A84C64C_1650013536008_Screenshot+128.png)


On the right of the **Permissions** page, we copy the **Collection ID**, which we need to perform operations on the collection’s documents.

Next, we go to our **Attributes** tab to create the properties we want a document to have. 

![Appwrite Attributes Tab](https://paper-attachments.dropbox.com/s_77D59B81C7D6F6CD823AD3176FAA8A00421C016326171AF8CEA737818A84C64C_1650013554208_Screenshot+129.png)


We create a string attribute of **orderStatus**.


![Appwrite Console Attributes](https://paper-attachments.dropbox.com/s_77D59B81C7D6F6CD823AD3176FAA8A00421C016326171AF8CEA737818A84C64C_1650039665717_Screenshot+184.png)



## Creating our food ordering notification application.

Our food notification application will have two pages. One page will place an order and update the order’s status, which could either be accepted, prepared, en route, or delivered. The second page subscribes to the order status and notifies us about any updates to it in real-time.

**Creating our Order Page**

Our order page will consist of a button to place an order and a select list to update our order status.

We create a `views` folder in our `src` folder to create this user interface. This `src/views` folder will contain a `Home.vue` file.

We create our order page in our `src/views/Home.vue` file.

```vue
    <template>
      <div class="home rounded overflow-hidden shadow-lg">
        <div class="home-container">
          <button type="button" class="bg-gray-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Place Order</button>
          <h3>Change Order Status to follow your order</h3>
          <select name="order-status" id="order-status" class ="relative w-150 bg-white border border-gray-300 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
            <option value="Order Placed"  >Order Placed</option>
            <option value="Order Accepted" >Order Accepted</option>
            <option value="Prepared" >Prepared</option>
            <option value="En-route" >En-route</option>
            <option value="Delivered">Delivered</option>
          </select>
        </div>
      </div>
    </template>

    <style>
      #app {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 80vh;
        text-align: center;
      }

      .app-container{
        height: 400px;
      }

      .page-link{
        margin: 10px;
        text-decoration: underline;
      }
    </style>

```
## Routing to our pages 

In our `src/main.js` file, we enable our application to use `router` to navigate to different pages with this piece of code.

```js
    import { createApp } from 'vue'
    import App from './App.vue'
    import router from './router'
    import './index.css';
     
    createApp(App).use(router).mount('#app')
```
Next, we create a `router` folder in our `src` folder. Our `router` folder will contain an `index.js` file. This file will be responsible for defining what routes lead to specific components.

```js
    // src/router/index.js
    import { createRouter,createWebHistory } from 'vue-router'
    import Home from '@/views/Home'
    import Notification from '@/views/Notifications'
    const routes = [
        {
            path: '/',
            name: 'Home',
            component: Home
        }
    ]
    const router = createRouter({
        history: createWebHistory(process.env.BASE_URL),
        routes
    })
    export default router;
```
This code block above does the following:

- Creates a `routes` array containing the route's path and the component we want the route to lead to.
- Facilitate routing using Vue `createRouter` method, which receives an object as a parameter, this object has two fields, a history field and a route field whose value is our route array.

Next, in our `App.vue` file, we add the links that will link to these pages.

```vue
    <template>
      <div class="app-container">
        <router-view ></router-view>
        <router-link class="page-link" to="/">  Home Page</router-link>
      </div>
    </template>

    <script>
      export default {
        name: 'App',
      }
    </script>

    <style>
      #app {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 80vh;
        text-align: center;
      }

      .app-container{
        height: 400px;
      }

      .page-link{
        margin: 10px;
        text-decoration: underline;
      }
    </style>
    
```

- The router-view component represents our component. In the code block above, we specify what position our app is situated.
- The `router-link ` component acts as an anchor tag by creating links leading to different routes.

To avoid errors resulting from non-multi-level component names, create an `..eslintrc.js` file in our `views` folder.

This `views/..eslintrc.js` file will contain this code.

```js
    module.exports = {
      rules: {
        'vue/multi-word-component-names': 0,
      },
    }
```
Go to our [http://localhost:8080/](http://localhost:8080/) to see our Home page.

![Food notifications app home page](https://paper-attachments.dropbox.com/s_77D59B81C7D6F6CD823AD3176FAA8A00421C016326171AF8CEA737818A84C64C_1650041159104_Screenshot+185.png)


**Creating our Notifications page**

In our `src/views` folder, we create a `Notifications.vue` file to display our order’s status.

```vue
    <template>
        <div class="notifications rounded overflow-hidden shadow-lg">
            <div class="order-container">
                <h2>Order Placed</h2>
            </div>
        </div>
    </template>

    <style scoped>
        .notifications{
            width: 350px;
            height: 100px;
            display: flex;
            justify-content: center;
            align-items: center;
        }
    </style>
```

We add a route to the `routes` object in the `src/router/index.js` file to view our notifications page in the browser.

```js
    // src/router/index.js
    
    const routes = [
        {
            path: '/',
            name: 'Home',
            component: Home
        },
        {
            path: '/notification-page',
            name: 'Notification',
            component: Notification
        }
    ]
```
Here is how our `src/router/index.js` file looks.

```js
    import { createRouter,createWebHistory } from 'vue-router'
    import Home from '@/views/Home'
    import Notification from '@/views/Notifications'
    const routes = [
        {
            path: '/',
            name: 'Home',
            component: Home
        },
        {
            path: '/notification-page',
            name: 'Notification',
            component: Notification
        }
    ]
    const router = createRouter({
        history: createWebHistory(process.env.BASE_URL),
        routes
    })
    export default router;
```

Next, we add another `router-link` in our `App.vue` file as an anchor tag to link to the notifications page.

```vue
    <template>
      <div class="app-container">
        <router-view ></router-view>
        <router-link class="page-link" to="/">  Home Page</router-link>
    
        <!-- link to notification page -->
    
        <router-link class="page-link" to="/notification-page">Notification Page</router-link>
      </div>
    </template>
```
To see our notifications page, go to http://localhost:8080/notification-page.

![Food notification page](https://paper-attachments.dropbox.com/s_77D59B81C7D6F6CD823AD3176FAA8A00421C016326171AF8CEA737818A84C64C_1650041270040_Screenshot+186.png)

## Add interaction with our database

When a user places an order, we create a document in our database that holds the string "Order Placed". Any updates to the order status using the select list will automatically update the document on our Appwrite database.

Our `src/views/Home.vue` file will be responsible for creating and updating our database document.

**Creating our database document**

```vue
    <script>
      import { sdk } from "../../utils";
      export default {
        name: 'Home',
        data(){
            return{
              documentID: null,
            }
          },
          methods: {
            placeOrder: async function(){
              try {
                let promise = await sdk.database.createDocument('collectionID', 'unique()', {
                  "orderStatus": "Order Placed"
                })
                
                this.documentID = promise.$id;
                alert("order successfully placed")
              } catch (error) {
                console.log(error)
              }
            },
            updateOrder: async function(e){
              try {
                let orderValue = e.target.value 
                await sdk.database.updateDocument('collectionID', this.documentID, {
                  "orderStatus": orderValue
                })
                alert(orderValue);
                
              } catch (error) {
                console.log(error)
              }
            }
          }
          
      }
    </script>
```
In the code block above, we do the following:

- Create a `documentID` variable in our `data` object to contain our newly created document ID
- Create a `placeOrder` function to create a document in our database using Appwrite’s `createDocument`
- Create a `updateOrder` function that we will pass into the select list later in this tutorial where the function collects the value of an option element and uses it to update our document on the database

In our `Home.vue` file, we pass the `placeOrder` function to an on-click event listener on our button and our `updateDocument` function to an on-change event listener on our `select` list.

```vue
    <template>
      <div class="home rounded overflow-hidden shadow-lg">
        <div class="home-container">
          <button type="button" @click = 'placeOrder' class="bg-gray-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Place Order</button>
          <h3>Change Order Status to follow your order</h3>
          <select name="order-status" id="order-status" v-on:change = "updateOrder" class ="relative w-150 bg-white border border-gray-300 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
            <option value="Order Placed"  >Order Placed</option>
            <option value="Order Accepted" >Order Accepted</option>
            <option value="Prepared" >Prepared</option>
            <option value="En-route" >En-route</option>
            <option value="Delivered">Delivered</option>
          </select>
        </div>
      </div>
    </template>
```
Here is how our `Home.vue` file looks.

(https://gist.github.com/Iheanacho-ai/9e47a71adb7fc17b1c5df85c7387dfef)

We place an order to see our document in our database.

![Food Notification Page](https://paper-attachments.dropbox.com/s_77D59B81C7D6F6CD823AD3176FAA8A00421C016326171AF8CEA737818A84C64C_1650042334990_Screenshot+185.png)



![Food notification Page](https://paper-attachments.dropbox.com/s_77D59B81C7D6F6CD823AD3176FAA8A00421C016326171AF8CEA737818A84C64C_1650042435286_Screenshot+140.png)


**Subscribing to updates on the document**

We subscribe to our Appwrite database document in our `src/views/Notifications.vue`.

```vue
    <script>
        import { sdk } from "../../utils";
    
        export default {
            name: 'Notification',
            data(){
                return{
                    orderStatus: ""
                }
            },
            mounted(){
                if(sdk.account.get !== null){
                    try {
                    sdk.subscribe('collections.collectionID.documents', response => {
                        this.orderStatus = response.payload.orderStatus
                    });
                    
                    } catch (error) {
                    console.log(error, 'error')
                    }
                }
            }
        }
    </script>
```
In the code block above, we do the following:

- Create an `orderStatus` variable to hold our response payload
- Check if we have an active user session before subscribing to our collections channel
- Subscribe to a channel using Appwrite's `subscribe` method, which receives two parameters, the channel we subscribe to and a callback function; to understand more about the various channels we can subscribe to, check out [Appwrite's Realtime Channels](https://appwrite.io/docs/realtime#channels?utm_source=hackmamba&utm_medium=blog&utm_campaign=vue-food-notification)
- Updates our `orderStatus` variable

Next, we render our `orderStatus` variable as text in our `Notifications.vue` file.

```vue
    <template>
        <div class="notifications rounded overflow-hidden shadow-lg">
            <div class="order-container">
                <h2>{{orderStatus}}</h2>
            </div>
        </div>
    </template>
```
Here is how our `Notifications.vue` file looks.

(https://gist.github.com/Iheanacho-ai/0f23cf72b2a43be4251f2fb2aa22b7d8)

**Creating our Notification**

To use the mosha-vue-toastify notification in our application, we import it into our `src/views/Notifications.vue`

```vue
    import { sdk } from "../../utils";
    import { createToast } from 'mosha-vue-toastify';
    import 'mosha-vue-toastify/dist/style.css'
```
We use the `watch` object to listen for a variable change in our application. By listening to the `orderStatus` variable, we can fire off the `createToast` method every time the variable is updated.

```vue
    export default {
        name: 'Notification',
        data(){
            return{
                orderStatus: ""
            }
        },
        watch: {
            orderStatus: function(){
                createToast(this.orderStatus)
            }
        },
        mounted(){
           ...
        }
    }

```
Here is how our application looks.


![Food notification application](https://paper-attachments.dropbox.com/s_77D59B81C7D6F6CD823AD3176FAA8A00421C016326171AF8CEA737818A84C64C_1650014209670_first-notif.gif)
![Food notification application](https://paper-attachments.dropbox.com/s_77D59B81C7D6F6CD823AD3176FAA8A00421C016326171AF8CEA737818A84C64C_1650014203906_second-notif.gif)

## Conclusion

This article discussed using Appwrite’s Realtime feature to subscribe to application events.

## Resources
- [Getting started with Appwrite for web](https://appwrite.io/docs/getting-started-for-web)
- [Mosha Vue Toastify](https://www.npmjs.com/package/mosha-vue-toastify)
- [What is Appwrite and](https://appwrite.io/docs/getting-started-for-web)[,](https://appwrite.io/docs/getting-started-for-web) [How to use it Efficiently.](https://appwrite.io/docs/getting-started-for-web)