How I created this app

Creating an entire blog-style web app can be kind of difficult and very time-consuming. So we have to decide what kind of technology do we want to use and what kind of abstraction level. Maybe trying a simple WordPress site or a Ghost one with a template from their store can be sufficient. But this is not the objective of the page. I mean, obviously I want to reuse code and do not invent the wheel again, but I want to demonstrate how to create a cool web app with modern technology.

I thought about creating my own backend using Feathers framework (have you ever tried it? give it a chance!), I've been working with it some years and I love it. Its richful ecosystem and real-time api is at other level. Or maybe a simple Express app (Feathers uses express behind it) with Mongoose. A classic choice, right?

But I wanted to focus more in the front end and not having to think about security issues, modeling the database or writing a powerful content editor that only I am going to see and use. That's why I decided to host my own Ghost instance and create a custom front end app for it using Vue with Nuxt. Let's start by checking the technology stack.

Technology stack

  • A Ghost app for the backend.
  • A MySQL database to store the content of the posts.
  • Docker and Docker swarm to operate the Ghost app with its database.
  • Nginx to work with my SSL certificates and secure the data.
  • Digital Ocean to have cloud infrastructure as a service.
  • Vue + Nuxt + Vuetify for the front end app.
  • Netlify for front end devops.

Backend

The backend was really easy, I have it running in a couple of minutes thanks to the ready to use docker images. This is my docker-compose.yml file:

version: "3.3"
services:
  ghost:
    image: "ghost:3"
    deploy:
      restart_policy:
        condition: on-failure
    environment:
      admin__url: <my-api-url>
      database__client: mysql
      database__connection__host: db
      database__connection__user: <my-db-user>
      database__connection__password: <my-db-password>
      database__connection__database: ghost
    ports:
      - 2368:2368
    volumes:
      - content:/var/lib/ghost/content
  db:
    image: mysql:5.7
    deploy:
      restart_policy:
        condition: on-failure
    environment:
      MYSQL_ROOT_PASSWORD: <my-db-password>
    volumes:
      - database:/var/lib/mysql
volumes:
  content:
  database:

And with a simple docker stack deploy blog -c docker-compose.yml you can have your own Ghost instance running.

To have SSL certificates working I used Certbot and followed its simple instructions to make it work with an Nginx instance running in the host machine (not inside the containers).

Front end

I love writing front end code, it's like a hobby. But that is thanks to Vue. What an amazing progressive framework. I've been using it for a time, I started using it back in 2018 to make some simple widgets and web apps but I fell in love with it.

Nuxt

Now, for this app, I wanted to try new things and one of the first ones to came to my mind was Nuxt, the progressive Vue framework. What? A framework for a framework? That escalated quickly.

Yeah, it's weird to have a framework for a framework (welcome to Javascript (?)), but in this case it can be very useful. Apart from giving to you more structure in your code it has server-side rendering that can help with the speed of what the user see something in its screen.

Without going deeper about server-side rendering, you can think that, in simple terms, when the user requests a page of your app the server renders the components of the view in its side and sends an html document with more than the simple div where Vue has to load your app. It's faster than simple sending your JS files because the user immediately sees content, he does not have to wait for the first render after all the components and file chunks are obtained from the backend.

Composition api

If you already know Vue you probably know that the version 3 is coming! Indeed, the beta is already out. One of the great things that will come with the third major version of the framework is the composition api.

If you have worked with React you probably heard about the new hooks. Well, Vue is making its own efforts to provide the composition api. You can read more in the RFC but in simple terms you can check the power of the new api by a simple image:

Probably you were in this situation:

You started with a very simple component that perform a single action like search. You have your input and a simple search button.
Then you want to add the filters to your search component so the user can search with some tags or hashtags for example and you have to add the new methods, maybe new computed properties and the new data for this functionality.

This is a typical case where the options api have a problem. The component is doing two things that are related (so they are in the same component) but their logic is separated in the options of the component.  What about now if you want to split the component because is getting bigger and bigger and you want to reuse your code and logic but in different components? Probably you should extract some data properties, then some methods and start creating the new components.

But that's the problem, your logic is distributed all along the big component. That is the explanation of the image, the color indicates related logic/code, and at the left -with the options api- you can see how it is distributed and at the right you have all your logic in a single place.

This is because with the composition api you can have simple composition functions that contains all the logic related to a single thing and it does not have to share the component's options like data and methods.

A very simple example is this composition function that I used here, in this exact app:

import { ref } from '@vue/composition-api'

/**
 * A composition function to change the color of the component
 * when the user scroll down the app.
 *
 * It must be used with the `v-scroll` directive of vuetify.
 * See: https://vuetifyjs.com/en/directives/scrolling/
 *
 * Example:
 *
 * ```javascript
 * <template>
 *   <div v-scroll="setColorOnScroll">{{ color }}</div>
 * </template>
 *
 * <script>
 * export default {
 *   setup() {
 *     return {
 *       ...useSetColorOnScroll({ start: 'transparent', end: 'blue' })
 *     }
 *   }
 * }
 * <//script>
 * ```
 *
 * @param {Object} [options]
 * @param {String} [options.start] color, when the app is at the top.
 * @param {String} [options.end] color to set when the user scroll downs.
 *
 * @returns {Object} with the `color` to use in the app and a callback to
 *  change the color called `setColor` that receives the event generated
 *  by the `v-scroll` directive.
 */
export default function useSetColorOnScroll ({ start = 'transparent', end = 'blue' } = {}) {
  const color = ref(start)

  /**
   * Set the color according to the `scrollTop` property of the target
   * present in the scroll event.
   *
   * @param {Event} event triggered by the scroll action.
   * See: https://vuetifyjs.com/en/directives/scrolling/
   */
  function setColorOnScroll (event) {
    color.value = !event.target.documentElement.scrollTop ? start : end
  }

  return { color, setColorOnScroll }
}

And now every component that want to change its color when the user scrolls down can use it! As simple as that. Indeed, I used to set the color of the app bar, because when you entered this site in the home page the app bar didn't have a color, it was transparent. Cool, right?

Here is another example to easily get the context of a module in the store:

/**
 * Get the `state` object and the `commit`, `dispatch` and `getters` functions for the
 * module with the given namespace from the store.
 *
 * @param {String} namespace of the module of the store.
 * @param {Object} context of the setup method of the component.
 * See: https://composition-api.vuejs.org/api.html#setup
 */
export default (namespace, context) => {
  const store = context.root.$store
  const state = store.state[namespace]
  const commit = (mutation, payload) => store.commit(`${namespace}/${mutation}`, payload)
  const dispatch = (action, payload) => store.dispatch(`${namespace}/${action}`, payload)
  const getters = getter => store.getters[`${namespace}/${getter}`]

  return { commit, dispatch, getters, state }
}

You can even use composition functions inside other composition functions:

import { computed } from '@vue/composition-api'
import useStore from '~/compositions/useStore'

/**
 * Get the post to use from the store.
 *
 * @param {Object} context provided in the setup method
 * to get the store.
 * @returns {Object} with the `post` instance and the `id`
 * of the post.
 */
function usePost (context) {
  const id = context.root.$route.params.id
  const { state, dispatch } = useStore('posts', context)
  const post = computed(() => state.keyedById[id])

  // Dispatch the get actions. The action is smart and will not trigger
  // the api call if the item is already in the store.
  dispatch('get', { id, include: 'tags' })

  return { id, post }
}

Vuetify

To avoid inventing the wheel again I started with the awesome Vuetify component framework for Vue. It has really cool ready-to-use components some very useful style standards to work with themes and difficult responsive displays.

Source code

I did not describe all the decisions I made, how I wrote components, how I managing my page transitions and all the future plans and ideas that I want to implement in this site. But if you are curious and want to inspect the front end code, well, you can! I published it on GitHub so any one can use ideas or code that I've been using here. Give it a look!

While I develop the components to show my contact information you can contact me at my email fab.souto@gmail.com. Don't hesitate to ask me anything, I can't promise to respond quickly but I'm going to answer you!