Real-time notifications help users know when a change occurs in your app. You can implement real-time notifications in different ways.
This article teaches you how to implement notifications using Appwrite, an open-source backend server, and Nuxt3, a hybrid vue framework.
Repository
https://github.com/folucode/real-time-appwrite-nuxt3
Prerequisites
- Basic understanding of CSS, JavaScript, and Nuxt.js.
- Docker Desktop installed on your computer (run the
docker -v
command to verify if you have the docker desktop installed); if not, install it from here. - An Appwrite instance running on your computer. Check out the documentation to create a local Appwrite instance. You can also install using digital ocean or gitpod.
Setting up Nuxt3
Run the command below to set up a Nuxt3 app:
npx nuxi init appwrite-nuxt-app
Change directory:
cd appwrite-nuxt-app
Install the dependencies:
npm install
Now you'll be able to start a Nuxt3 development server with this command:
npm run dev
A browser window will automatically open at http://localhost:3000.
Also, install these dependencies below as you will need them later in the course of this tutorial:
npm install @nuxtjs/tailwindcss @tailvue/nuxt @iconify/vue appwrite --save
Setting up Appwrite Database
Follow the instructions below to create the Database:
- Go to your
Appwrite
Console. - Click on Create Project and give your project a name.
- On the left side of the dashboard, click on Database and create a database.
- Click on the Add Collection button. This action redirects us to a Permissions page.
- At the Collection level, assign Read and Write Access with role:all.
- On the right of your Permissions page, copy the Collection ID, which you would need to perform operations on documents in this collection.
- Next, go to the
attributes
tab to create the properties you want a document to have. - Click on Add Attribute and then New String Attribute. Give it an ID called message and a Size of your choice.
Creating the Application
Open App.vue
, located at the root of the project folder, and replace the content with the code below. It is a template with two buttons referencing two methods you would create later.
<template>
<div>
<button
type="button"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
@click="createDocument"
>
Create Document
</button>
<button
type="button"
class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
@click="deleteDocument"
>
Delete Document
</button>
</div>
</template>
Open a script tag in the same file and then import and initialize the Appwrite
modules like so:
...
<script>
import { Client, Account, Databases } from 'appwrite';
const client = new Client();
client
.setEndpoint('http://localhost/v1') // Your Appwrite Endpoint
.setProject('[PROJECT_ID]');
const account = new Account(client);
const database = new Databases(client, '[DATABASE_ID]');
</script>
You can get the PROJECT_ID
and DATABASE_ID
from your Appwrite console.
Interacting with the Database
Only signed-in users can interact with the Appwrite database but you can create an anonymous session as a workaround for this.
In the same App.vue
file, you can create an anonymous session using the method below. Add the code in the script tag, just below the initialization of the Appwrite
modules:
...
account.createAnonymousSession().then(
(response) => {
console.log(response);
},
(error) => {
console.log(error);
}
);
</script>
Setting up a Realtime Connection
In the App.vue
file, export a default object. The data method
returns an object with a message
property and gives it a 'Welcome!' value.
In the mounted
method, check to make sure there is an active user session. If so, set up a real-time connection with Appwrite
by subscribing to the documents channel.
In the callback, assign the message
variable a new value containing the timestamp of the database event.
...
export default {
data() {
return {
message: 'Welcome!',
};
},
mounted() {
if (account.get !== null) {
try {
client.subscribe('documents', (response) => {
this.message = `This event was called at ${response.timestamp}`;
});
} catch (error) {
console.log(error, 'error');
}
}
},
};
</script>
Listing the Documents
In the current file, add a new property called notifications
and give it a value of an empty array. In the Nuxt.js methods
object, define a new method called listDocuments
and paste the code below in it:
...
export default {
data() {
return {
message: 'Welcome!',
notifications: [],
};
},
methods: {
async listDocuments() {
try {
this.notifications = [];
let response = await database.listDocuments('[COLLECTION_ID]');
response.documents.map((document) =>
this.notifications.push(document.$id)
);
console.log(this.notifications);
} catch (error) {
console.log(error);
}
},
mounted() {
...
</script>
This method does the following:
- First, empty the notifications array. Then, you use Appwrite’s
listDocuments
method with yourCOLLECTION_ID
to get all the documents. - Map through the documents, push them into the notifications array, and log them to the console.
Creating new Documents
Create a new method called createDocument
just below the listDocuments
method and paste the code below into it:
async createDocument() {
try {
await database.createDocument('[COLLECTION_ID]', 'unique()', {
message: 'Welcome!',
});
this.listDocuments();
} catch (error) {
console.log(error);
}
},
This method does the following:
- The Appwrite
createDocument
method takes in the collection ID and the document payload to create a new document. Theunique()
param generates a random document ID. - Calls the
listDocuments
method. - Logs an error if creating the document fails.
Deleting Documents in the Collection
Create a new method called deleteDocument
and paste the code below into it:
async deleteDocument() {
if (this.notifications.length > 0) {
try {
let documentID = this.notifications[this.notifications.length - 1];
await database.deleteDocument('[COLLECTION_ID]', documentID);
this.listDocuments();
} catch (error) {
console.log(error);
}
} else {
alert('database is empty');
}
},
The deleteDocument
function does the following:
- Checks if the
notifications
array length is greater than zero. If it is, it gets the last document ID in the array and stores it in adocumentID
variable. If the notifications array length is less than zero, the method shows an alert that the Database is empty. - Deletes the document using the Appwrite
deleteDocument()
method. ThisdeleteDocument
method receives a collection ID and the document ID parameter. - Calls the
listDocuments
function. - Logs an error if deleting the document fails.
After following all these steps, your App.vue
file should look like this:
<template>
<div>
<button
type="button"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
@click="createDocument"
>
Create Document
</button>
<button
type="button"
class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
@click="deleteDocument"
>
Delete Document
</button>
</div>
</template>
<script>
import { Client, Account, Databases } from 'appwrite';
const client = new Client();
client
.setEndpoint('http://localhost/v1') // Your Appwrite Endpoint
.setProject('[PROJECT_ID]');
const account = new Account(client);
const database = new Databases(client, '[DATABASE_ID]');
account.createAnonymousSession().then(
(response) => {
console.log(response);
},
(error) => {
console.log(error);
}
);
export default {
data() {
return {
message: 'Welcome!',
notifications: [],
};
},
methods: {
async listDocuments() {
try {
this.notifications = [];
let response = await database.listDocuments('[COLLECTION_ID]');
response.documents.map((document) =>
this.notifications.push(document.$id)
);
console.log(this.notifications);
} catch (error) {
console.log(error);
}
},
async createDocument() {
try {
await database.createDocument('[COLLECTION_ID]', 'unique()', {
message: 'Welcome!',
});
this.listDocuments();
} catch (error) {
console.log(error);
}
},
async deleteDocument() {
if (this.notifications.length > 0) {
try {
let documentID = this.notifications[this.notifications.length - 1];
await database.deleteDocument('[COLLECTION_ID]', documentID);
this.listDocuments();
} catch (error) {
console.log(error);
}
} else {
alert('database is empty');
}
},
},
mounted() {
if (account.get !== null) {
try {
client.subscribe('documents', (response) => {
this.message = `This event was called at ${response.timestamp}`;
});
} catch (error) {
console.log(error, 'error');
}
}
},
};
</script>
Creating the Notifications
In the nuxt.config.ts
, replace the contents with the code below:
import { defineNuxtConfig } from 'nuxt';
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
meta: [{ name: 'description', content: 'My Appwrite site.' }],
modules: ['@nuxtjs/tailwindcss', '@tailvue/nuxt'],
});
'@nuxtjs/tailwindcss'
and '@tailvue/nuxt'
are the packages you installed at the beginning of this article. You'll use them to create the toast notifications.
Create a new file in the project root called tailwind.config.js
and paste the code below into it:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['node_modules/tailvue/dist/tailvue.es.js'],
theme: {
extend: {},
},
plugins: [],
};
The toast package uses tailwindcss
.
Showing the Notifications
First, add a new property in the data
, return the object called toast
and give it the value of an empty string. Destructure $toast
from the useNuxtApp
in the mounted
method and assign it to the **toast**
****property you added to the data
method:
mounted() {
const { $toast } = useNuxtApp();
this.toast = $toast;
...
},
In a watch
method which Nuxt.js provides, watch for changes on the message
property and then show the new message in a toast:
watch: {
message: function (message) {
this.toast.show({
type: 'success',
message,
timeout: 2,
});
},
},
The final App.vue
final should look like this:
<template>
<div>
<button
type="button"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
@click="createDocument"
>
Create Document
</button>
<button
type="button"
class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
@click="deleteDocument"
>
Delete Document
</button>
</div>
</template>
<script>
import { Client, Account, Databases } from 'appwrite';
const client = new Client();
client
.setEndpoint('http://localhost/v1') // Your Appwrite Endpoint
.setProject('[PROJECT_ID]');
const account = new Account(client);
const database = new Databases(client, '[DATABASE_ID]');
account.createAnonymousSession().then(
(response) => {
console.log(response);
},
(error) => {
console.log(error);
}
);
export default {
data() {
return {
message: 'Welcome!',
notifications: [],
toast: '',
};
},
methods: {
async listDocuments() {
try {
this.notifications = [];
let response = await database.listDocuments('[COLLECTION_ID]');
response.documents.map((document) =>
this.notifications.push(document.$id)
);
console.log(this.notifications);
} catch (error) {
console.log(error);
}
},
async createDocument() {
try {
await database.createDocument('[COLLECTION_ID]', 'unique()', {
message: 'Welcome!',
});
this.listDocuments();
} catch (error) {
console.log(error);
}
},
async deleteDocument() {
if (this.notifications.length > 0) {
try {
let documentID = this.notifications[this.notifications.length - 1];
await database.deleteDocument('[COLLECTION_ID]', documentID);
this.listDocuments();
} catch (error) {
console.log(error);
}
} else {
alert('database is empty');
}
},
},
mounted() {
const { $toast } = useNuxtApp();
this.toast = $toast;
if (account.get !== null) {
try {
client.subscribe('documents', (response) => {
this.message = `This event was called at ${response.timestamp}`;
});
} catch (error) {
console.log(error, 'error');
}
}
},
watch: {
message: function (message) {
this.toast.show({
type: 'success',
message,
timeout: 2,
});
},
},
};
</script>
Here is what the app should look like:
Conclusion
This article showed you how Appwrite's real-time feature creates notifications in Nuxt3.