Skip to content

8. ServerMiddleware vs Middleware

Khriztian Moreno edited this page Nov 8, 2017 · 2 revisions

Middleware

The middleware allows you to define custom functions that can be executed before rendering a page or a group of pages.

Each middleware must be placed in the middleware / directory. The name of the file will be the name of the middleware(middleware/i18n.js).

We will use this concept to add the internalization of our site using vue-i18n.

  • Run in terminal: npm i -S vue-i18n
  • We create a middleware called i18n.js with the following content that will be executed before rendering any page
export default function ({ app, store, params, error }) {
  // Get locale from params
  const locale = params.lang || 'en'
  if (store.state.locales.indexOf(locale) === -1) {
    return error({ message: 'This page could not be found.', statusCode: 404 })
  }
  // Set locale
  store.commit('SET_LANG', locale)
  app.i18n.locale = store.state.locale
}
  • We add a plugin i18n.js and register it in nuxt.config.js
import Vue from 'vue'
import VueI18n from 'vue-i18n'

Vue.use(VueI18n)

export default ({ app, store }) => {
  // Set i18n instance on app
  // This way we can use it in middleware and pages asyncData/fetch
  app.i18n = new VueI18n({
    locale: store.state.locale,
    fallbackLocale: 'en',
    messages: {
      'en': require('~/locales/en.json'),
      'es': require('~/locales/es.json')
    }
  })
}
  • We create the json files that will have our translation in the local folder for both the English language en.json and Spanish es.json:
{
  "LINKS": {
    "HOME": "Home",
    "POST": "Post"
  },
  "HOME": {
    "TITLE": "Home",
    "INTRODUCTION": "Welcome to Gixt Blog!!",
    "BY": "By",
    "COMMENTS": "Comments",
    "READ_MORE": "Read More",
    "SEARCH": "Search"
  },
  "POST": {
    "TITLE": "Post",
    "AUTHOR": "Author",
    "COMMENTS": "Comments"
  }
}
  • And finally in our store we add the necessary elements for the correct functioning of the internalization
const createStore = () => {
  return new Vuex.Store({
    state: {
      gists: [],
      // add
      locales: ['en', 'es'],
      locale: 'en'
    },
    ...
    mutations: {
      ...
      // add
      SET_LANG (state, locale) {
        if (state.locales.indexOf(locale) !== -1) {
          state.locale = locale
        }
      },
      ...
    }
  • Now it's just changing the texts present in the GistArticle.vue component by the t() function of i18n
<template>
  <article class="post post-large">
    <div v-if="gist">
      <div class="post-date">
        <span class="day">{{ gist.created_at | date('D') }}</span>
        <span class="month">{{ gist.created_at | date('MMM') }}</span>
      </div>
      <div class="post-content">
        <h2>
          <nuxt-link :to="{ path: '/post/'+ gist.id }">{{gist.description}}</nuxt-link>
        </h2>
        <!-- Content Raw -->
        <p>{{gist.description}}</p>
        <!-- /Content Raw -->
        <div class="post-meta">
          <span>
          <i class="fa fa-user"></i>
          {{ $t('HOME.BY') }} <a :href="gist.owner.html_url" target="_blank">{{ gist.owner.login }}</a>
          </span>
          <span>
          <i class="fa fa-comments"></i>
          <a :href="gist.comments_url" target="_blank">{{ gist.comments }} {{ $t('HOME.COMMENTS') }}</a>
          </span>
          <a :href="'/post/'+ gist.id +''" class="btn btn-xs btn-primary pull-right">{{ $t('HOME.READ_MORE') }}...</a>
        </div>
      </div>
    </div>
  </article>
</template>

ServerMiddleware

Are just running in server side before vue-server-renderer and can be used for server specific tasks like handling API requests or serving assets.

To better demonstrate this property, let's use the post/create page by creating a simple API in Express.js that allows us to create a Gist directly on GitHub.

  • serverMiddleware in file nuxt.config.js
const bodyParser = require('body-parser')

module.exports = {
  ...
  // Agregar
  serverMiddleware: [
    // body-parser middleware
    bodyParser.json(),
    // Api middleware
    '~/api'
  ],
}

We are going to have this file tree in our api folder (in the root of the project):

api/
--| gist.js
--| index.js
  • Install the necessary dependencies for our api with express.js
$ npm i -S body-parser express request-promise
  • gist.js
const { Router } = require('express')
const request = require('request-promise')

const router = Router()

// Personal access tokens: https://github.com/settings/tokens
const TOKEN_GITHUB = '3ab7b782c21a3b3f329129654494dc5ca79e977c'

function create (req, res, next) {
  const gist = {
    description: req.body.description,
    public: true,
    files: req.body.files
  }

  const options = {
    url: 'https://api.github.com/gists',
    method: 'POST',
    headers: {
      'Authorization': `token ${TOKEN_GITHUB}`,
      'User-Agent': 'khriztianmoreno'
    },
    json: true,
    body: gist
  }

  return request(options)
    .then(response => res.status(200).json(response))
    .catch(error => res.status(500).json(error))
}

router.post('/gist/', create)

module.exports = router

It is necessary to create a GitHub personal access tokens in order to create the gist associated with a user, otherwise it would be created anonymously GitHub personal access tokens

  • index.js
const express = require('express')
const gist = require('./gist')

// Create express router
const router = express.Router()

// Transform req & res to have the same API as express
// So we can use res.status() & res.json()

const app = express()
router.use((req, res, next) => {
  Object.setPrototypeOf(req, app.request)
  Object.setPrototypeOf(res, app.response)
  req.res = res
  res.req = req
  next()
})

// Add Gist Routes
router.use(gist)

// Export the server middleware
module.exports = {
  path: '/api',
  handler: router
}

To achieve an appearance of editor markdown in our form we are going to install these libraries

$ npm i -S marked lodash
  • Finally our creation form of a new Gist pages/post/create.vue would look like this:
<template>
  <div>
    <form name="form" @submit.prevent="onSubmit()">
      <div class="form-group">
        <label for="name">Title</label>
        <input type="text" v-model.trim="title" class="form-control" id="name" placeholder="Enter title" required>
      </div>
      <div class="form-group">
        <label for="description">Description</label>
        <div class="row">
          <div class="col-sm-6">
            <textarea :value="input" @input="update" name="description" rows="10">
            </textarea>
          </div>
          <div class="col-sm-6">
            <div id="editor-result" v-html="compiledMarkdown"></div>
          </div>
        </div>
      </div>
      <button type="submit" class="btn btn-primary" >Guardar</button>
    </form>
  </div>
</template>

<script>
  import axios from 'axios'
  import marked from 'marked'
  import _ from 'lodash'

  export default {
    data () {
      return {
        input: '# hello',
        title: ''
      }
    },
    computed: {
      compiledMarkdown: function () {
        return marked(this.input, { sanitize: true })
      }
    },
    methods: {
      update: _.debounce(function (e) {
        this.input = e.target.value
      }, 300),
      async onSubmit () {
        const file = `${this.title.toLowerCase().replace(/\s/g, '-')}.md`

        const gist = {
          description: this.title,
          files: {}
        }
        gist.files[file] = { content: this.input }

        try {
          await axios.post('/api/gist', gist)
          alert('Yeah!!')
        } catch (error) {
          console.log('Error', error)
        }
      }
    }
  }
</script>

<style lang="scss" scoped>
  #editor {
    margin: 0;
    height: 100%;
    font-family: "Helvetica Neue", Arial, sans-serif;
    color: #333;

    &-result {
      border: 1px solid;
      height: 100%;
      padding: 10px;
    }
  }

  textarea,
  #editor div {
    display: inline-block;
    width: 100%;
    height: 100%;
    vertical-align: top;
    box-sizing: border-box;
    padding: 0 20px;
  }

  textarea {
    border: none;
    border-right: 1px solid #ccc;
    resize: none;
    outline: none;
    background-color: #f6f6f6;
    font-size: 14px;
    font-family: "Monaco", courier, monospace;
    padding: 20px;
  }

  code {
    color: #f66;
  }
</style>

To see these steps complete, you can change to the 8-serverMiddleware-middleware branch in this repository.