Creating stunning blogs with Appwrite’s database relationships and Nuxt

Emmanuel Ugwu - May 19 '23 - - Dev Community

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 command node -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>
Enter fullscreen mode Exit fullscreen mode

Navigate to the project directory and install the required dependencies.

cd <project-name> 
npm install
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then, we’ll set up a new Appwrite project by starting an Appwrite instance and navigating to the specified hostname and port:

    localhost:80
Enter fullscreen mode Exit fullscreen mode

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.

Log in or sign up

On Appwrite’s console, click the Create Project button, input blog as the project name, and click Create.

Creating a new project

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.

Settings tab
Project ID and API Endpoint

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.

Create a database
Database name

Next, we’ll create two Collections — Blog and Posts.

Blog Collection
Post Collection

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.

Update Permissions

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.

Title attribute
Content attribute

For the Blog collection, generate a string attribute — blog-name with a size of 256 bits.

Blog attribute

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.

Create a 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.

Creating a title document

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.

Creating a comment document
Created Document

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>
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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:

Posts section

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>
Enter fullscreen mode Exit fullscreen mode

This is what our application will look like after applying the configurations mentioned above:

Article section

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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:

Final Demo

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.

Resources

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .