DEV Community

Cover image for Creating a microblog using Vue + Supabase
Ademílson F. Tonato
Ademílson F. Tonato

Posted on • Edited on

Creating a microblog using Vue + Supabase

At the beginning of the year, I was reading some articles about new tools in the frontend world, when I came across Supabase. I confess that my first thought was "more of the same", and this thought remained until I found this article: Supabase raises $6M for its open-source Firebase alternative.

After reading the article, the thought became, "If Mozilla is investing in this project, it is because they realized how promising it could become!". It was then that I decided to read the documentation and try to explore a little more.

Supabase is an open-source Firebase alternative for those who don't know. It includes support for PostgreSQL databases and authentication tools, with storage and serverless solution coming soon.

How to adventure into something new using the knowledge I already have?

It was my thought when I decided to explore, and my idea was to create a microblog using Vue + Supabase.

How to start?

First, I created a new project using Vue CLI, included Vue Router to manage the routes of the application and TailwindCSS to add some basic style to the blog without wasting a lot of time. At the end of it, my directory structure was this:

Initial directory structure

I will stick to explaining the technology integration part (Vue + Supabase) and ignoring the reasons for having created an architecture that way or about different approaches to doing the same thing.

Configuring the database

After accessing the Supabase dashboard, which I had previously explored, I followed the guide it presents and created my organization and my project. I named them, respectively, as supabase-test and supabase-vue.

Followed by the creation of both, we're able to connect between the project and our application. Still, I decided to create my table, called posts, and add some fictional data to have information to consume in addition to the columns.

As you can see in the image below, the interface is friendly. Allowing us to browse the sidebar quickly, see what organization and project we are in (this information is present in the top left of the dashboard), and mainly configure our table or insert, edit, or even delete records in a straightforward way.

Viewing my posts table

Structuring the responsibility of the components

As I mentioned above, I used TailwindCSS to add basic styles and avoid wasting time. My purpose was to test the interaction between technologies and not build something so beautiful.

In the image of our structure, it was possible to see some files and components. We will detail the responsibility of each one of them:

Directory src/components:

  • Footer.vue - It is the global footer of our application. It will present my name, two icons with links to my social networks, and a simple copyright text.
  • Header.vue - It is the global header of our application. It will present the project name, and on the home page, it will display a container with the blog title.
  • List.vue - This component is the application's initial route, called posts-list. It is responsible for searching the information in our database and making a loop of the ListItem component.
  • ListItem.vue - It is responsible for presenting three pieces of information, referring to each publication. They are: Title, description, and a button that will take us to the publication page.
  • Post.vue - The second route of our application, named post-item, is the one that presents the complete information of each publication.
  • App.vue - It is the component responsible for calling the other components.

Directory src/*:

  • main.js - The default file, suggested by Vue, to start our application.
  • router.js - The route file presents the basic settings for our two routes and a fallback so that any invalid URL redirects to the main route.

This was the essential list with the responsibilities of each file. Let's see how it turned out in practice?

Viewing the rendered component list

Installing dependencies and configuring the application's connection to the database

The first thing to do is install the dependency. As we are in a project that uses JavaScript, we will use an isomorphic library for the connection between our database and the project maintained by the Supabase team, called supabase-js and which offers a very simple and straightforward documentation.

npm install @supabase/supabase-js
Enter fullscreen mode Exit fullscreen mode

Installation completed. It's time to import the library and call a method, passing the data from our database (URL and KEY) and we will have immediate access.

import { createClient } from '@supabase/supabase-js'

// Create a single supabase client for interacting with your database
const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key')
Enter fullscreen mode Exit fullscreen mode

I decided to do this in the simplest way possible¹, I created a file called /src/Database.service.js with the following code:

  1. Important note: I know this service could be a singleton to avoid problems, but, as I said above, the idea is to explore more about the integration between technologies.
import { createClient } from '@supabase/supabase-js'

export default class DatabaseService {
    constructor() {
        this.database = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_SECRET_KEY)
    }

    getInstance() {
        return this.database;
    }
}
Enter fullscreen mode Exit fullscreen mode

Using the connection and fetching all posts

With our file that initializes the connection configured, use the database instance and create a method to fetch the posts.

Our List.vue file will be responsible for searching the data and passing the information of each publication to the ListItem.vue component.

<template>
  <section class="px-2 pt-16 pb-6 bg-white md:px-0">
    <div class="container items-center max-w-6xl px-8 mx-auto xl:px-5">

      <template v-for="(post, index) in posts">
        <list-item :key="`post-${index}`" :id="post.id" :title="post.title" :description="post.description" />
      </template>

    </div>
  </section>
</template>

<script>
import ListItem from "./ListItem";
import DatabaseService from "../Database.service";

export default {
  name: "List",
  components: {
    ListItem,
  },
  data: () => ({
    posts: [],
    database: null,
  }),
  created() {
    const database = new DatabaseService();
    this.database = database.getInstance();
  },
  async mounted() {
    await this.fetchPosts();
  },
  methods: {
    async fetchPosts() {
      const { error, data } = await this.database
        .from("posts")
        .select()
        .order("id");

      if (error) {
        console.error(error)
        return
      }

      this.setPosts(data)
    },
    setPosts(posts) {
      this.posts = posts;
    }
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

In summary, the component above, during its creation, calls our service, which opens the connection to the database, while in the mounted we call the fetchPosts method, responsible for fetching all data from the table posts ordered by id (order: ASC).

After fetching the data, we add it to the posts array. Our DOM will react to the change in the viable one and iterate over it, passing the correct information to each ListItem component.


At the same time, our ListItem.vue component, responsible for rendering each of the publications on our list, follows this structure:

<template>
  <div class="flex flex-wrap items-center sm:-mx-3 mt-12">
    <div class="w-full">
      <div class="w-full pb-6 space-y-6 lg:space-y-8 xl:space-y-9 sm:pr-5 lg:pr-0 md:pb-0">
        <h1
          class="text-4xl font-extrabold tracking-tight text-gray-900 sm:text-5xl md:text-4xl lg:text-5xl xl:text-4xl"
        >
          <span class="block xl:inline">{{ title }}</span>
        </h1>
        <p
          class="mx-auto text-base text-gray-500 sm:max-w-md lg:text-xl md:max-w-full"
        >{{ getShortDescription }}</p>
        <div class="relative flex flex-col sm:flex-row sm:space-x-4">
          <router-link
            :to="{ name: 'post-item', params: { title, description } }"
            class="flex items-center w-full px-6 py-3 mb-3 text-lg text-white bg-indigo-600 rounded-md sm:mb-0 hover:bg-indigo-700 sm:w-auto"
          >
            Read the article
            <svg
              xmlns="http://www.w3.org/2000/svg"
              class="w-5 h-5 ml-1"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <line x1="5" y1="12" x2="19" y2="12" />
              <polyline points="12 5 19 12 12 19" class />
            </svg>
          </router-link>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "ListItem",
  props: {
    title: String,
    description: String,
  },
  computed: {
    getShortDescription() {
      return `${this.description.substr(0, 100)}...`;
    },
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

The first important thing to note is that we receive two parameters: title and description, these are the data that were previously sent for each post, and will be rendered as follows (as we have already seen in the previous image).

One of the items in the list of publications displayed on the first page of the application


Another detail that we must take into account is the link of the button Read the article, which uses the component <router-link> provided by Vue Router and which allows us to direct the user to the route called post-item, also sending two parameters: title and description.

The route called post-item, will load the component Post.vue, showing the received parameters (title and description). Let's look at the code and the visual result:

<template>
  <div
    class="flex items-center justify-center py-10 text-white bg-white sm:py-16 md:py-24 lg:py-32"
  >
    <div class="relative max-w-3xl px-10 text-center text-white auto lg:px-0">
      <div class="flex flex-col w-full md:flex-row">
        <div class="flex justify-between">
          <h1 class="relative flex flex-col text-6xl font-extrabold text-left text-black">
            {{ $route.params.title }}
          </h1>
        </div>

      </div>

      <div class="my-16 border-b border-gray-300 lg:my-24"></div>

      <p
        class="text-left text-gray-500 xl:text-xl"
      >
      {{ $route.params.description }}
      </p>
    </div>
  </div>
</template>

<script>
export default {
  name: "Post",
};
</script>
Enter fullscreen mode Exit fullscreen mode

Publication preview page

So far, everything has been relatively simple and useful for the proposed experiment.


What is the final result of our application visually?

Navigability of the microblog

Conclusions and the future

Although our application has been entirely experimental and we have not explored all the features of Supabase. It is possible to notice enormous ease, both in Supabase's dashboard and in the interaction between the application and the database.

Supabase is quite recent, but it looks extremely promising. It currently offers the possibility of creating a backend in less than 2 minutes with Postgres Database, Authentication, instant APIs, and realtime subscriptions. Soon it will still give us Storage and serverless functions, therefore, keep your eyes and ears open for news about this project.

If you are interested in the source code of the project we created, go to the project directory on GitHub and implement even more exciting things, such as:

  • Realtime Subscriptions - To show a new publication whenever it is inserted in the database (I also wrote an article on how to do this).
  • Admin panel - An area reserved for the blog administrator to insert, edit or delete publications using the Authentication functionality.
  • Pagination of publications on our main page.
  • Etc.

In summary, there is a multitude of things that can be done. Let your imagination guide you and when you are finished, share with us <3

If you have any question, do not hesitate to contact me (ademilsonft@outlook.com / @ftonato) or the Supabase team).

Top comments (3)

Collapse
 
lukesnider profile image
Luke Snider

This is amazing. Does Supabase currently support static web hosting?

Collapse
 
ftonato profile image
Ademílson F. Tonato

Hey @lukesnider

Check out the video below and you'll see it happening in practice.

Deploy a full-stack Next.js application with a PostgreSQL database in less than 3 minutes.

Collapse
 
lukesnider profile image
Luke Snider

How did I not know about this before! This is so cool. Definitely going to play around with supabase and vercel. I currently host quite a few projects within Firebase.

Thank you for the article! Well written and informative read.