Databases are essential to modern web app development, providing a centralized location for data storage, management, and retrieval. They are crucial, as without databases, web apps would have to store data in files or memory, which may become unmanageable as data increases.
In this article, we’ll build a blog using Appwrite’s database relationship to store and render data, as well as Pink Design, an open-source design system to style a Nuxt project.
The complete source code is located on GitHub. Clone and fork it to get started.
Prerequisites
To follow along, the following are required:
- Docker Desktop installed on the computer; run the
docker -v
command to verify that we have Docker installed. If not, install it from the Get Docker documentation. - A basic understanding of JavaScript, Vue.js, and Nuxt.
- An Appwrite instance; check out this article on how to set up an instance. Appwrite also supports one-click installation on DigitalOcean or Gitpod.
- Node and its package manager,
npm
. Run the commandnode -v && npm -v
to verify that we have them installed or install them from here.
Getting started
Project setup and installation
Run the following command in the terminal to create a new Nuxt application:
npx nuxi init <project-name>
Navigate to the project directory and install the required dependencies.
cd <project-name>
npm install
Run npm run dev
to start a development server at https://localhost:3000/ in our browser.
NOTE:
<project-name>
above stands for the name of our app; we can call it any name we deem fit.
What is Pink Design?
Pink Design is Appwrite's open-source design system for creating consistent and reusable user interfaces. Pink Design aims to emphasize collaboration, developer experience, and accessibility.
Scaffolding an Appwrite Project
Installing Appwrite
Appwrite is a development platform that provides a powerful API and management console for building backend servers for web and mobile applications.
To use Appwrite in our Nuxt application, install Appwrite’s Pink Design and client-side SDK (Software Development Kit) for web applications.
npm install appwrite
npm install @appwrite.io/pink
Then, we’ll set up a new Appwrite project by starting an Appwrite instance and navigating to the specified hostname and port:
localhost:80
NOTE: To set up an Appwrite instance, follow the steps in the article located in Prerequisites.
Creating a new project
Next, we must log into our account or create an account by initializing an Appwrite instance if we don’t have one.
On Appwrite’s console, click the Create Project button, input blog as the project name, and click Create.
The project dashboard will appear on the console. Next, click the Settings tab at the bottom of the page and copy the Project ID and API Endpoint; we’ll use this to set up our Nuxt application.
Creating a Collection and Attribute
On the left side of the Appwrite Console dashboard, click on the Database tab. Click on the Create Database button to create a new database. Creating a new database will lead us to the Collection page.
Next, we’ll create two Collections — Blog and Posts.
After creating the Collections, go to the Update Permissions section on the Settings page. We want to assign a CRUD access (create, read, update, delete) with a role: any value for the Collections we created earlier. We can customize these roles later to specify who has access to read or write to our database.
Next, go to the Attributes tab of the Post collection to create the properties we want the document to have. Let’s generate string attributes — title and content with a size of 1000 and 20000 bits.
For the Blog collection, generate a string attribute — blog-name with a size of 256 bits.
Next, create a Relationship attribute for the Blog collection. Select Relationship as the attribute type. Select the Two-way relationship type in the Relationship modal and pick post as the related collection and post as an attribute key. Select an attribute key called blog to represent the related collection and one-to-many as the relation. Select Cascade as the desired on-delete behavior. Click the Create button to create the relationship.
Let’s head to the Documents section and click on Create Document. We want to create a post title for the blog. Fill in the attributes with the dummy title we made for the blog.
Head to the Post collection and create a Document with a title and content value. Select the Blog document ID we created earlier as the blog relationship value.
The documents created above are for posts for the blog.
Building the UI
Create a pages/index.vue
file at the root folder of the project with the following syntax:
<template>
<main>
<nav class="u-flex u-cross-center u-main-space-between u-padding-32">
<h2 class="logo u-padding-32 eyebrow-heading-1 u-color-text-pink">
<NuxtLink to="/">Pink Blog</NuxtLink>
</h2>
<NuxtLink to="/">
<span class="button"> WRITE AN ARTICLE </span>
</NuxtLink>
</nav>
<div class="container">
</div>
</main>
</template>
The Pink Design CSS styles above do the following:
- container: It holds any content and is used to group content and create scannable interfaces
- eyebrow-heading-1: Used to support the main heading of a page or to provide additional context or orientation
- u-flex, u-grid: Controls how an element is displayed
- u-cross-center u-main-space-between: Aligns and justifies an element’s content
- u-text-center: Centers a text
- u-padding-32: Applies a padding of 2rem to the element
- u-color-text-pink: Changes a text color to pink
Fetch and render data from Appwrite
Here, we’ll fetch the posts created earlier on from Appwrite. To do this, create a new file, utils.js
, in the root directory of the project, then copy and paste the following code:
<script setup>
import { Client, Databases } from "appwrite";
const client = new Client();
const databases = new Databases(client);
client
.setEndpoint("OUR_API_ENDPOINT") // Your API Endpoint
.setProject("OUR_PROJECT_ID"); // Your project ID
export const getPost = databases.listDocuments( "OUR_DATABASE_ID", "OUR_COLLECTION_ID", "OUR_DOCUMENT_ID");
Let’s break down the code snippet above:
- Imports the required module, Client and Databases
- Creates a new instance for the modules
NOTE: Get your
API Endpoint
,Project ID
,[DATABASE_ID]
, and[COLLECTION_ID]
from Appwrite’s console.
Import the instance in the index.vue
file, which interacts with Appwrite services whenever the component is mounted.
import { getPost } from "../utils";
const docs = ref(null);
onMounted(() => {
getPost.then(
function (response) {
docs.value = response;
},
function (error) {
console.log(error);
}
);
});
</script>
Next, we’ll display the posts from the Appwrite console using the v-for directive. We’ll also render the latest post at the top of the list.
<template>
<main>
<nav>
... // nav bar
</nav>
<div class="container">
<h2 class="eyebrow-heading-1 u-text-center u-padding-64">posts</h2>
<ul class="list">
<li class="box" v-for="doc in docs.slice().reverse()" :key="doc.$id">
<NuxtLink :to="`/post/${doc.$id}`">
<div class="u-flex u-cross-center u-main-space-between">
<div class="u-grid">
<span class="text eyebrow-heading-1"> {{ doc.title }} </span>
<span class="text">{{ doc.content.slice(1, 200) }}</span>
</div>
<span class="button icon-cheveron-right" aria-hidden="true"></span>
</div>
</NuxtLink>
</li>
</ul>
</div>
</main>
</template>
The Pink Design CSS styles within the <template>
do the following:
- heading-level-3: Determines the size of the text
- u-line-height-2: Adjusts the line height of a text
- button: A class representing a button
- icon-cheveron-right: Creates a chevron right button
The application will look like this after applying the configurations above:
Posting new content to Appwrite
Here, we'll build a form to post content to Appwrite. To do this, create a pages/article.vue
file and paste the following code:
<template>
<div>
<nav> ... // nav </nav>
<form @submit.prevent="submitForm">
<h2 class="eyebrow-heading-1 u-text-center u-padding-64">
write an article
</h2>
<div class="u-grid u-cross-center u-main-center u-padding-16">
<label for="title">Article title:</label>
<input placeholder="Title" type="text" id="title" v-model="title" required />
<label for="content">Content:</label>
<textarea class="input-text" placeholder="Content" type="content" id="content"
v-model="content" required />
<button class="button" type="submit">
<span class="text">Submit</span>
</button>
</div>
</form>
</div>
</template>
This is what our application will look like after applying the configurations mentioned above:
Next, we’ll assign the form data values to two separate variables and push the values to Appwrite. Let’s also clear the form once the data is sent.
<script>
import { Client, Databases, ID } from "appwrite";
export default {
data() {
return {
title: "",
content: "",
};
},
methods: {
async submitForm() {
const client = new Client();
const databases = new Databases(client);
client
.setEndpoint("OUR_API_ENDPOINT") // Your API Endpoint
.setProject("OUR_PROJECT_ID"); // Your project ID
try {
const response = await databases.createDocument(
"OUR_DATABASE_ID",
"OUR_COLLECTION_ID",
ID.unique(),
{
title: this.title,
content: this.content,
}
);
console.log(response);
this.name = "";
this.comment = "";
} catch (error) {
console.error(error);
}
},
},
};
</script>
Head back to the Post section after pushing the new post to Appwrite. This ection will contain an updated version of the posts.
Applying dynamic routing
We want to click on each post in the blog and view its content. Create a pages/post/[id].vue
file to render a post’s content when clicked. Copy and paste the code snippet below:
<template>
<main>
<nav>
<h2 class="logo u-padding-32 eyebrow-heading-1 u-color-text-pink">
<NuxtLink to="/">Pink Blog</NuxtLink>
</h2>
</nav>
<div v-if= "isIdMatch">
<h2 class="heading-level-3 u-text-left u-padding-64">{{ post.title }}</h2>
<p class="heading-level-7 u-padding-64" style="line-height: 2.5rem">
{{ post.content }}
</p>
</div>
</main>
</template>
<script>
import { useRouter, useRoute } from "vue-router";
import { Client, Databases } from "appwrite";
const router = useRouter();
const route = useRoute();
const id = route.params.id;
const isIdMatch = router.currentRoute.value.params.id === id;
const post = ref(null)
const client = new Client();
const databases = new Databases(client);
client
.setEndpoint("OUR_API_ENDPOINT") // Your API Endpoint
.setProject("OUR_PROJECT_ID"); // Your project ID
const singlePost = databases.getDocument(
"OUR_DATABASE_ID",
"OUR_COLLECTION_ID",
"`${id}`"
);
onMounted(() => {
singlePost.then(
function (response) {
post.value = response;
console.log(post.value); // Success
},
function (error) {
console.log(error); // Failure
}
);
});
</script>
Here, the code snippet will do the following:
- Gets the route ID of the clicked post.
- Creates an instance to access the post via its route ID.
- Renders the post only if the fetched post’s ID matches the route id.
The application will look like this after applying the configurations above:
Conclusion
Appwrite’s Relationships feature provides a database management system that helps reduce redundant information while creating data sets. Pink Design's flexible framework promotes development and responsiveness while providing a broad range of customization. This combination allows us to build applications with exciting UIs swiftly, without hassle.